From 1279742774fa5bf8484e38c2bcb8c1951f153c45 Mon Sep 17 00:00:00 2001 From: Mark Sailes <45629314+msailes@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:18:15 +0100 Subject: [PATCH 001/173] Adds the V2 version of the pre token generation event. (#465) --- ...nitoUserPoolPreTokenGenerationEventV2.java | 134 ++++++++++++++++++ .../lambda/runtime/tests/EventLoader.java | 4 + .../lambda/runtime/tests/EventLoaderTest.java | 15 +- ...er_pool_pre_token_generation_event_v2.json | 33 +++++ 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java create mode 100644 aws-lambda-java-tests/src/test/resources/cognito_user_pool_pre_token_generation_event_v2.json diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java new file mode 100644 index 000000000..c72505703 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CognitoUserPoolPreTokenGenerationEventV2.java @@ -0,0 +1,134 @@ +/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.Map; + +/** + * Represent the class for the Cognito User Pool Pre Token Generation Lambda Trigger V2 + *

+ * See Pre Token Generation Lambda Trigger + */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@ToString(callSuper = true) +public class CognitoUserPoolPreTokenGenerationEventV2 extends CognitoUserPoolEvent { + /** + * The request from the Amazon Cognito service. + */ + private Request request; + + /** + * The response from your Lambda trigger. + */ + private Response response; + + @Builder(setterPrefix = "with") + public CognitoUserPoolPreTokenGenerationEventV2( + String version, + String triggerSource, + String region, + String userPoolId, + String userName, + CallerContext callerContext, + Request request, + Response response) { + super(version, triggerSource, region, userPoolId, userName, callerContext); + this.request = request; + this.response = response; + } + + @Data + @EqualsAndHashCode(callSuper = true) + @NoArgsConstructor + @ToString(callSuper = true) + public static class Request extends CognitoUserPoolEvent.Request { + + private String[] scopes; + private GroupConfiguration groupConfiguration; + private Map clientMetadata; + + @Builder(setterPrefix = "with") + public Request(Map userAttributes, String[] scopes, GroupConfiguration groupConfiguration, Map clientMetadata) { + super(userAttributes); + this.scopes = scopes; + this.groupConfiguration = groupConfiguration; + this.clientMetadata = clientMetadata; + } + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class GroupConfiguration { + /** + * A list of the group names that are associated with the user that the identity token is issued for. + */ + private String[] groupsToOverride; + /** + * A list of the current IAM roles associated with these groups. + */ + private String[] iamRolesToOverride; + /** + * Indicates the preferred IAM role. + */ + private String preferredRole; + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class Response { + private ClaimsAndScopeOverrideDetails claimsAndScopeOverrideDetails; + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class ClaimsAndScopeOverrideDetails { + private IdTokenGeneration idTokenGeneration; + private AccessTokenGeneration accessTokenGeneration; + private GroupOverrideDetails groupOverrideDetails; + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class IdTokenGeneration { + private Map claimsToAddOrOverride; + private String[] claimsToSuppress; + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class AccessTokenGeneration { + private Map claimsToAddOrOverride; + private String[] claimsToSuppress; + private String[] scopesToAdd; + private String[] scopesToSuppress; + } + + @Data + @AllArgsConstructor + @Builder(setterPrefix = "with") + @NoArgsConstructor + public static class GroupOverrideDetails { + private Map groupsToOverride; + private Map iamRolesToOverride; + private String preferredRole; + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index 7228fb90d..aa600749c 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -113,6 +113,10 @@ public static RabbitMQEvent loadRabbitMQEvent(String filename) { return loadEvent(filename, RabbitMQEvent.class); } + public static CognitoUserPoolPreTokenGenerationEventV2 loadCognitoUserPoolPreTokenGenerationEventV2(String filename) { + return loadEvent(filename, CognitoUserPoolPreTokenGenerationEventV2.class); + } + public static T loadEvent(String filename, Class targetClass) { if (!filename.endsWith("json")) { diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 3177b9ccc..1c9d17e16 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -14,7 +14,6 @@ import static java.time.Instant.ofEpochSecond; import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assertions.from; import com.amazonaws.services.lambda.runtime.events.*; @@ -363,4 +362,18 @@ public void testLoadRabbitMQEvent() { assertThat(header1.get("bytes")).contains(118, 97, 108, 117, 101, 49); assertThat((Integer) headers.get("numberInHeader")).isEqualTo(10); } + + @Test + public void testLoadCognitoUserPoolPreTokenGenerationEventV2() { + CognitoUserPoolPreTokenGenerationEventV2 event = EventLoader.loadCognitoUserPoolPreTokenGenerationEventV2("cognito_user_pool_pre_token_generation_event_v2.json"); + assertThat(event).isNotNull(); + assertThat(event) + .returns("2", from(CognitoUserPoolPreTokenGenerationEventV2::getVersion)) + .returns("us-east-1", from(CognitoUserPoolPreTokenGenerationEventV2::getRegion)) + .returns("TokenGeneration_Authentication", from(CognitoUserPoolPreTokenGenerationEventV2::getTriggerSource)); + + CognitoUserPoolPreTokenGenerationEventV2.Request request = event.getRequest(); + String[] requestScopes = request.getScopes(); + assertThat("aws.cognito.signin.user.admin").isEqualTo(requestScopes[0]); + } } diff --git a/aws-lambda-java-tests/src/test/resources/cognito_user_pool_pre_token_generation_event_v2.json b/aws-lambda-java-tests/src/test/resources/cognito_user_pool_pre_token_generation_event_v2.json new file mode 100644 index 000000000..43f8e0f7d --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito_user_pool_pre_token_generation_event_v2.json @@ -0,0 +1,33 @@ +{ + "version": "2", + "triggerSource": "TokenGeneration_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_EXAMPLE", + "userName": "JaneDoe", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "1example23456789" + }, + "request": { + "userAttributes": { + "sub": "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111", + "cognito:user_status": "CONFIRMED", + "email_verified": "true", + "phone_number_verified": "true", + "phone_number": "+12065551212", + "family_name": "Zoe", + "email": "Jane.Doe@example.com" + }, + "groupConfiguration": { + "groupsToOverride": ["group-1", "group-2", "group-3"], + "iamRolesToOverride": ["arn:aws:iam::123456789012:role/sns_caller1", "arn:aws:iam::123456789012:role/sns_caller2", "arn:aws:iam::123456789012:role/sns_caller3"], + "preferredRole": ["arn:aws:iam::123456789012:role/sns_caller"] + }, + "scopes": [ + "aws.cognito.signin.user.admin", "openid", "email", "phone" + ] + }, + "response": { + "claimsAndScopeOverrideDetails": [] + } +} \ No newline at end of file From 5c53c44e69dbd6a5bb61afda52f3f5ee2cad5af3 Mon Sep 17 00:00:00 2001 From: Andrea Culot <95755271+andclt@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:00:22 +0100 Subject: [PATCH 002/173] Stage aws-lambda-java-events 3.11.6 (#483) --- README.md | 2 +- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b2145ce16..445e35f97 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.11.5 + 3.11.6 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 6519bcebd..8c60b3a23 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -69,7 +69,7 @@ com.amazonaws aws-lambda-java-events - 3.11.5 + 3.11.6 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index bc814dd0e..a19bd260a 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 11, 2024 +`3.11.6`: +- Add the V2 version of the pre token generation event([#465](https://github.com/aws/aws-lambda-java-libs/pull/465)) + ### April 12, 2024 `3.11.5`: - Add requestHeaders field for Appsync lambda authorizer event([#473](https://github.com/aws/aws-lambda-java-libs/pull/473)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 4167a3d51..2c7442c06 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.11.5 + 3.11.6 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 23be8e597..b77032b73 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.11.5 + 3.11.6 org.junit.jupiter diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 669c0979b..1dc96d3d5 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.11.5 + 3.11.6 From 9a5450a070e03765135f3bca9636a425b3c4540e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 28 Jun 2024 18:31:15 +0100 Subject: [PATCH 003/173] release 2.5.1 (#487) --- .github/dependabot.yml | 7 + .github/workflows/aws-lambda-java-core.yml | 9 +- ...aws-lambda-java-events-sdk-transformer.yml | 7 +- .github/workflows/aws-lambda-java-events.yml | 7 +- .github/workflows/aws-lambda-java-log4j2.yml | 7 +- .../aws-lambda-java-serialization.yml | 7 +- .github/workflows/aws-lambda-java-tests.yml | 7 +- .github/workflows/repo-sync.yml | 4 + ...runtime-interface-client_merge_to_main.yml | 9 +- .../workflows/runtime-interface-client_pr.yml | 15 ++- .github/workflows/samples.yml | 3 +- .gitignore | 5 +- README.md | 2 +- .../Makefile | 19 ++- .../README.md | 4 +- .../RELEASE.CHANGELOG.md | 5 + .../pom.xml | 40 +++++- .../ric-dev-environment/publish_snapshot.sh | 6 +- .../services/lambda/crac/ContextImpl.java | 43 +++--- .../services/lambda/crac/DNSManager.java | 10 ++ .../lambda/runtime/api/client/UserFault.java | 21 ++- .../api/client/runtimeapi/JniHelper.java | 64 +++++++++ .../api/client/runtimeapi/NativeClient.java | 62 +-------- .../src/main/jni/Dockerfile.glibc | 9 +- .../src/main/jni/Dockerfile.musl | 9 +- .../src/main/jni/build-jni-lib.sh | 13 +- ...zonaws_services_lambda_crac_DNSManager.cpp | 27 ++++ ...mazonaws_services_lambda_crac_DNSManager.h | 19 +++ ...ime_api_client_runtimeapi_NativeClient.cpp | 22 +--- ...ntime_api_client_runtimeapi_NativeClient.h | 4 + .../src/main/jni/macro.h | 14 ++ .../services/lambda/crac/ContextImplTest.java | 21 ++- .../lambda/crac/DNSCacheManagerTest.java | 124 ++++++++++++++++++ .../runtime/api/client/UserFaultTest.java | 40 ++++++ .../codebuild/buildspec.os.alpine.yml | 2 +- .../codebuild/buildspec.os.amazoncorretto.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.1.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.2.yml | 2 +- .../test/integration/test-handler/pom.xml | 2 +- 39 files changed, 525 insertions(+), 150 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h create mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/macro.h create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..3722537ae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 0b553bbc3..39ff12914 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-core/**' + - 'aws-lambda-java-core/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-core/**' + - 'aws-lambda-java-core/**' + - '.github/workflows/aws-lambda-java-core.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto @@ -38,3 +39,5 @@ jobs: - name: Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr + env: + IS_JAVA_8: true diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 679639d34..05a870add 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events-sdk-transformer/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events-sdk-transformer/**' + - '.github/workflows/aws-lambda-java-events-sdk-transformer.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index bdd01eb7f..634d803ea 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-events/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-events/**' + - 'aws-lambda-java-events/**' + - '.github/workflows/aws-lambda-java-events.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 427c7536b..ab86f200f 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-log4j2/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-log4j2/**' + - '.github/workflows/aws-lambda-java-log4j2.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 9d71cbabf..baa6052fc 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-serialization/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-serialization/**' + - '.github/workflows/aws-lambda-java-serialization.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index fc587e8e4..bf17ed2a6 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -7,11 +7,12 @@ on: push: branches: [ main ] paths: - - 'aws-lambda-java-tests/**' + - 'aws-lambda-java-tests/**' pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-tests/**' + - 'aws-lambda-java-tests/**' + - '.github/workflows/aws-lambda-java-tests.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 91054f9ef..c667bcebd 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -3,6 +3,10 @@ name: Repo Sync on: schedule: - cron: "0 8 * * 1-5" # At 08:00 on every day-of-week from Monday through Friday + pull_request: + branches: [ '*' ] + paths: + - '.github/workflows/repo-sync.yml' workflow_dispatch: jobs: diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 1783f1cde..27b22fd78 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -15,6 +15,7 @@ on: branches: [ main ] paths: - 'aws-lambda-java-runtime-interface-client/**' + workflow_dispatch: jobs: @@ -29,13 +30,13 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -48,9 +49,11 @@ jobs: - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build + env: + IS_JAVA_8: true - name: Issue AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ secrets.AWS_REGION }} role-to-assume: ${{ secrets.AWS_ROLE }} diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 7a7f602e9..b935f4f7d 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -7,7 +7,8 @@ on: pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-runtime-interface-client/**' + - 'aws-lambda-java-runtime-interface-client/**' + - '.github/workflows/runtime-interface-client_pr.yml' jobs: @@ -17,7 +18,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto @@ -25,6 +26,8 @@ jobs: - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr + env: + IS_JAVA_8: true build: runs-on: ubuntu-latest @@ -32,13 +35,13 @@ jobs: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -51,9 +54,11 @@ jobs: - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build + env: + IS_JAVA_8: true - name: Save the built jar - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: aws-lambda-java-runtime-interface-client path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2171ae785..cd6655494 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -12,6 +12,7 @@ on: branches: [ '*' ] paths: - 'samples/kinesis-firehose-event-handler/**' + - '.github/workflows/samples.yml' jobs: build: @@ -21,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up JDK 1.8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 8 distribution: corretto diff --git a/.gitignore b/.gitignore index 2f2f0af47..371bed6b7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ dependency-reduced-pom.xml .project # OSX -.DS_Store \ No newline at end of file +.DS_Store + +# snapshot process +aws-lambda-java-runtime-interface-client/pom.xml.versionsBackup diff --git a/README.md b/README.md index 445e35f97..94f842fb3 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 ``` diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index e57daf3a8..b3a204213 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -4,6 +4,13 @@ ARCHITECTURE := $(shell arch) ARCHITECTURE_ALIAS := $($(shell echo "$(ARCHITECTURE)_ALIAS")) ARCHITECTURE_ALIAS := $(or $(ARCHITECTURE_ALIAS),amd64) # on any other archs defaulting to amd64 +# Java 8 does not support passing some args (such add --add-opens) so we need to clear them +ifeq ($(IS_JAVA_8),true) + EXTRA_LOAD_ARG := -DargLineForReflectionTestOnly="" +else + EXTRA_LOAD_ARG := +endif + # This optional module exports MAVEN_REPO_URL, MAVEN_REPO_USERNAME and MAVEN_REPO_PASSWORD environment variables # making it possible to publish resulting artifacts to a codeartifact maven repository -include ric-dev-environment/codeartifact-repo.mk @@ -15,7 +22,7 @@ target: .PHONY: test test: - mvn test + mvn test $(EXTRA_LOAD_ARG) .PHONY: setup-codebuild-agent setup-codebuild-agent: @@ -44,11 +51,11 @@ pr: test test-smoke .PHONY: build build: - mvn clean install - mvn install -P linux-x86_64 - mvn install -P linux_musl-x86_64 - mvn install -P linux-aarch64 - mvn install -P linux_musl-aarch64 + mvn clean install $(EXTRA_LOAD_ARG) + mvn install -P linux-x86_64 $(EXTRA_LOAD_ARG) + mvn install -P linux_musl-x86_64 $(EXTRA_LOAD_ARG) + mvn install -P linux-aarch64 $(EXTRA_LOAD_ARG) + mvn install -P linux_musl-aarch64 $(EXTRA_LOAD_ARG) .PHONY: publish publish: diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 13f8658f8..ae299c77e 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 @@ -160,7 +160,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 1fefd0e23..bb5794c20 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,8 @@ +### June 28, 2024 +`2.5.1` +- Runtime API client improvements: fix a DNS cache issue +- Runtime API client improvements: fix circular exception references causing stackOverflow + ### March 20, 2024 `2.5.0` - Runtime API client improvements ([#471](https://github.com/aws/aws-lambda-java-libs/pull/471)) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 29d20c8d8..a4f022a48 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 jar AWS Lambda Java Runtime Interface Client @@ -35,6 +35,8 @@ UTF-8 UTF-8 0.8.8 + 2.4 + 3.1.1 5.9.2 + --add-opens java.base/java.net=ALL-UNNAMED @@ -90,9 +97,22 @@ + + maven-install-plugin + org.apache.maven.plugins + ${maven-install-plugin.version} + + + maven-deploy-plugin + org.apache.maven.plugins + ${maven-deploy-plugin.version} + maven-surefire-plugin 3.0.0-M9 + + ${argLineForReflectionTestOnly} + org.junit.jupiter @@ -110,6 +130,24 @@ maven-antrun-plugin 1.7 + + build-jni-lib-for-tests + generate-test-sources + + run + + + + + + + + + + + + build-jni-lib prepare-package diff --git a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh index cf5969e1f..9d2f9837f 100755 --- a/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh +++ b/aws-lambda-java-runtime-interface-client/ric-dev-environment/publish_snapshot.sh @@ -18,6 +18,10 @@ else echo "Already -SNAPSHOT version" fi +# get the updated project version +snapshotProjectVersion=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +echo "Updated project version is ${snapshotProjectVersion}" + CLASSIFIERS_ARRAY=("linux-x86_64" "linux_musl-x86_64" "linux-aarch_64" "linux_musl-aarch_64") for str in "${CLASSIFIERS_ARRAY[@]}"; do @@ -36,7 +40,7 @@ mvn -B -X -P ci-repo \ -DgroupId=com.amazonaws \ -DartifactId=aws-lambda-java-runtime-interface-client \ -Dpackaging=jar \ - -Dversion=$projectVersion \ + -Dversion=$snapshotProjectVersion \ -Dfile=./target/aws-lambda-java-runtime-interface-client-$projectVersion.jar \ -Dfiles=$FILES \ -Dclassifiers=$CLASSIFIERS \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java index ac4bc6480..87fb0ff2f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/ContextImpl.java @@ -23,25 +23,9 @@ public class ContextImpl extends Context { @Override public synchronized void beforeCheckpoint(Context context) throws CheckpointException { - - List exceptionsThrown = new ArrayList<>(); - for (Resource resource : getCheckpointQueueReverseOrderOfRegistration()) { - try { - resource.beforeCheckpoint(this); - } catch (CheckpointException e) { - Collections.addAll(exceptionsThrown, e.getSuppressed()); - } catch (Exception e) { - exceptionsThrown.add(e); - } - } - - if (!exceptionsThrown.isEmpty()) { - CheckpointException checkpointException = new CheckpointException(); - for (Throwable t : exceptionsThrown) { - checkpointException.addSuppressed(t); - } - throw checkpointException; - } + executeBeforeCheckpointHooks(); + DNSManager.clearCache(); + System.gc(); } @Override @@ -87,4 +71,25 @@ private List getCheckpointQueueForwardOrderOfRegistration() { .map(Map.Entry::getKey) .collect(Collectors.toList()); } + + private void executeBeforeCheckpointHooks() throws CheckpointException { + List exceptionsThrown = new ArrayList<>(); + for (Resource resource : getCheckpointQueueReverseOrderOfRegistration()) { + try { + resource.beforeCheckpoint(this); + } catch (CheckpointException e) { + Collections.addAll(exceptionsThrown, e.getSuppressed()); + } catch (Exception e) { + exceptionsThrown.add(e); + } + } + + if (!exceptionsThrown.isEmpty()) { + CheckpointException checkpointException = new CheckpointException(); + for (Throwable t : exceptionsThrown) { + checkpointException.addSuppressed(t); + } + throw checkpointException; + } + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java new file mode 100644 index 000000000..4c1a6cd59 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/crac/DNSManager.java @@ -0,0 +1,10 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.crac; + +class DNSManager { + static native void clearCache(); +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java index bc95af572..fc8eb005a 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java @@ -4,6 +4,8 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.util.HashSet; +import java.util.Set; public final class UserFault extends RuntimeException { private static final long serialVersionUID = -479308856905162038L; @@ -65,6 +67,16 @@ public static String trace(Throwable t) { * the same object for convenience. */ public static T filterStackTrace(T t) { + return filterStackTrace(t, new HashSet<>(), new HashSet<>()); + } + + private static T filterStackTrace(T t, Set visited, Set visitedSuppressed) { + if (visited.contains(t)) { + return t; + } + + visited.add(t); + StackTraceElement[] trace = t.getStackTrace(); for (int i = 0; i < trace.length; i++) { if (trace[i].getClassName().startsWith(packagePrefix)) { @@ -78,12 +90,15 @@ public static T filterStackTrace(T t) { Throwable cause = t.getCause(); if (cause != null) { - filterStackTrace(cause); + filterStackTrace(cause, visited, visitedSuppressed); } Throwable[] suppressedExceptions = t.getSuppressed(); - for (Throwable suppressed : suppressedExceptions) { - filterStackTrace(suppressed); + for(Throwable suppressed: suppressedExceptions) { + if (!visitedSuppressed.contains(suppressed)) { + visitedSuppressed.add(suppressed); + filterStackTrace(suppressed, visited, visitedSuppressed); + } } return t; diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java new file mode 100644 index 000000000..82586d272 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/JniHelper.java @@ -0,0 +1,64 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; + +public class JniHelper { + + private static final String NATIVE_LIB_PATH = "/tmp/.libaws-lambda-jni.so"; + private static final String NATIVE_CLIENT_JNI_PROPERTY = "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI"; + + /** + * Unpacks JNI library from the JAR to a temporary location and tries to load it using System.load() + * Implementation based on AWS CRT + * (ref. ...) + * + * @param libsToTry - array of native libraries to try + */ + public static void load() { + String jniLib = System.getProperty(NATIVE_CLIENT_JNI_PROPERTY); + if (jniLib != null) { + System.load(jniLib); + } else { + String[] libsToTry = new String[]{ + "libaws-lambda-jni.linux-x86_64.so", + "libaws-lambda-jni.linux-aarch_64.so", + "libaws-lambda-jni.linux_musl-x86_64.so", + "libaws-lambda-jni.linux_musl-aarch_64.so" + }; + unpackAndLoad(libsToTry, NativeClient.class); + } + } + + private static void unpackAndLoad(String[] libsToTry, Class clazz) { + List errorMessages = new ArrayList<>(); + for (String libToTry : libsToTry) { + try (InputStream inputStream = clazz.getResourceAsStream( + Paths.get("/jni", libToTry).toString())) { + if (inputStream == null) { + throw new FileNotFoundException("Specified file not in the JAR: " + libToTry); + } + Files.copy(inputStream, Paths.get(NATIVE_LIB_PATH), StandardCopyOption.REPLACE_EXISTING); + System.load(NATIVE_LIB_PATH); + return; + } catch (UnsatisfiedLinkError | Exception e) { + errorMessages.add(e.getMessage()); + } + } + + for (int i = 0; i < libsToTry.length; ++i) { + System.err.println("Failed to load the native runtime interface client library " + libsToTry[i] + + ". Exception: " + errorMessages.get(i)); + } + System.exit(-1); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java index a311ff00d..10e6bafd4 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java @@ -5,14 +5,7 @@ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; - -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; import static com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl.USER_AGENT; @@ -21,60 +14,11 @@ * interactions with the Runtime API. */ class NativeClient { - private static final String NATIVE_LIB_PATH = "/tmp/.libaws-lambda-jni.so"; - public static final String NATIVE_CLIENT_JNI_PROPERTY = "com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.JNI"; static void init() { - loadJNILib(); + JniHelper.load(); initializeClient(USER_AGENT.getBytes()); } - - private static void loadJNILib() { - String jniLib = System.getProperty(NATIVE_CLIENT_JNI_PROPERTY); - if (jniLib != null) { - System.load(jniLib); - } else { - String[] libsToTry = new String[]{ - "libaws-lambda-jni.linux-x86_64.so", - "libaws-lambda-jni.linux-aarch_64.so", - "libaws-lambda-jni.linux_musl-x86_64.so", - "libaws-lambda-jni.linux_musl-aarch_64.so" - }; - unpackAndLoadNativeLibrary(libsToTry); - } - } - - - /** - * Unpacks JNI library from the JAR to a temporary location and tries to load it using System.load() - * Implementation based on AWS CRT - * (ref. ...) - * - * @param libsToTry - array of native libraries to try - */ - static void unpackAndLoadNativeLibrary(String[] libsToTry) { - - List errorMessages = new ArrayList<>(); - for (String libToTry : libsToTry) { - try (InputStream inputStream = NativeClient.class.getResourceAsStream( - Paths.get("/jni", libToTry).toString())) { - if (inputStream == null) { - throw new FileNotFoundException("Specified file not in the JAR: " + libToTry); - } - Files.copy(inputStream, Paths.get(NATIVE_LIB_PATH), StandardCopyOption.REPLACE_EXISTING); - System.load(NATIVE_LIB_PATH); - return; - } catch (UnsatisfiedLinkError | Exception e) { - errorMessages.add(e.getMessage()); - } - } - - for (int i = 0; i < libsToTry.length; ++i) { - System.err.println("Failed to load the native runtime interface client library " + libsToTry[i] + - ". Exception: " + errorMessages.get(i)); - } - System.exit(-1); - } - + static native void initializeClient(byte[] userAgent); static native InvocationRequest next(); diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index dd4fdb22e..1cfcfbb1d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -53,9 +53,16 @@ RUN /usr/bin/c++ -c \ -I${JAVA_HOME}/include/linux \ -I ./deps/artifacts/include \ com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp -o com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o && \ + /usr/bin/c++ -c \ + -std=gnu++11 \ + -fPIC \ + -I${JAVA_HOME}/include \ + -I${JAVA_HOME}/include/linux \ + -I ./deps/artifacts/include \ + com_amazonaws_services_lambda_crac_DNSManager.cpp -o com_amazonaws_services_lambda_crac_DNSManager.o && \ /usr/bin/c++ -shared \ -std=gnu++11 \ - -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o \ + -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o com_amazonaws_services_lambda_crac_DNSManager.o \ -L ./deps/artifacts/lib64/ \ -L ./deps/artifacts/lib/ \ -laws-lambda-runtime \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl index e15f6adc5..64725c140 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl @@ -54,9 +54,16 @@ RUN /usr/bin/c++ -c \ -I${JAVA_HOME}/include/linux \ -I ./deps/artifacts/include \ com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp -o com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o && \ + /usr/bin/c++ -c \ + -std=gnu++11 \ + -fPIC \ + -I${JAVA_HOME}/include \ + -I${JAVA_HOME}/include/linux \ + -I ./deps/artifacts/include \ + com_amazonaws_services_lambda_crac_DNSManager.cpp -o com_amazonaws_services_lambda_crac_DNSManager.o && \ /usr/bin/c++ -shared \ -std=gnu++11 \ - -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o \ + -o aws-lambda-runtime-interface-client.so com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.o com_amazonaws_services_lambda_crac_DNSManager.o \ -L ./deps/artifacts/lib/ \ -laws-lambda-runtime \ -lcurl \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 3b505e74d..b7dbb5a80 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -51,10 +51,21 @@ function build_for_libc_arch() { echo "multi-arch not requested, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." echo "enabling docker buildx often updates the docker api version, so assuming that docker cli is also too old to use --output type=tar, so doing alternative build-tag-run approach" image_name="lambda-java-jni-lib-${libc_impl}-${arch}" + + # GitHub actions is using dockerx build under the hood. We need to pass --load option to be able to run the image + # This args is NOT part of the classic docker build command, so we need to check against a GitHub Action env var not to make local build crash. + if [[ "${GITHUB_RUN_ID:+isset}" == "isset" ]]; then + EXTRA_LOAD_ARG="--load" + else + EXTRA_LOAD_ARG="" + fi + docker build --platform="${docker_platform}" \ -t "${image_name}" \ -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" + --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" ${EXTRA_LOAD_ARG} + + echo "Docker image has been successfully built" docker run --rm --entrypoint /bin/cat "${image_name}" \ /src/aws-lambda-runtime-interface-client.so > "${artifact}" diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp new file mode 100644 index 000000000..ccf5481b9 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.cpp @@ -0,0 +1,27 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +#include +#include "macro.h" +#include "com_amazonaws_services_lambda_crac_DNSManager.h" + +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_crac_DNSManager_clearCache + (JNIEnv *env, jclass thisClass) { + jclass iNetAddressClass; + jclass concurrentMap; + jfieldID cacheFieldID; + jobject cacheObj; + jmethodID clearMethodID; + CHECK_EXCEPTION(env, iNetAddressClass = env->FindClass("java/net/InetAddress")); + CHECK_EXCEPTION(env, concurrentMap = env->FindClass("java/util/concurrent/ConcurrentMap")); + CHECK_EXCEPTION(env, cacheFieldID = env->GetStaticFieldID(iNetAddressClass, "cache", "Ljava/util/concurrent/ConcurrentMap;")); + CHECK_EXCEPTION(env, cacheObj = (jobject) env->GetStaticObjectField(iNetAddressClass, cacheFieldID)); + CHECK_EXCEPTION(env, clearMethodID = env->GetMethodID(concurrentMap, "clear", "()V")); + CHECK_EXCEPTION(env, env->CallVoidMethod(cacheObj, clearMethodID)); + return; + + ERROR: + // we need to fail silently here + env->ExceptionClear(); +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h new file mode 100644 index 000000000..f26639ba9 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_crac_DNSManager.h @@ -0,0 +1,19 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +#include + +#ifndef _Included_com_amazonaws_services_lambda_crac_DNSManager +#define _Included_com_amazonaws_services_lambda_crac_DNSManager +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_crac_DNSManager_clearCache + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index 02cfeb7e2..db71b8194 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -1,27 +1,13 @@ /* - * Copyright 2019-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ #include +#include "macro.h" #include "com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h" #include "aws/lambda-runtime/runtime.h" #include "aws/lambda-runtime/version.h" -#define CHECK_EXCEPTION(env, expr) \ - expr; \ - if ((env)->ExceptionOccurred()) \ - goto ERROR; - static aws::lambda_runtime::runtime * CLIENT = nullptr; static jint JNI_VERSION = JNI_VERSION_1_8; diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h index 28a6f444a..27c636117 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h @@ -1,3 +1,7 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ #include #ifndef _Included_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h b/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h new file mode 100644 index 000000000..df5759afe --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/macro.h @@ -0,0 +1,14 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef _Included_macros +#define _Included_macros + +#define CHECK_EXCEPTION(env, expr) \ + expr; \ + if ((env)->ExceptionOccurred()) \ + goto ERROR; + +#endif diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java index b81660730..bad72ea20 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/ContextImplTest.java @@ -1,22 +1,35 @@ /* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - */ - +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ package com.amazonaws.services.lambda.crac; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mockito; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.doThrow; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; + +@DisabledOnOs(OS.MAC) public class ContextImplTest { private Resource throwsWithSuppressedException, noop, noop2, throwsException, throwCustomException; + @BeforeAll + public static void jniLoad() { + JniHelper.load(); + } + @BeforeEach public void setup() throws Exception { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java new file mode 100644 index 000000000..5eb6f749f --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java @@ -0,0 +1,124 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.crac; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper; + +import java.util.Map; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +@DisabledOnOs(OS.MAC) +public class DNSCacheManagerTest { + + static String CACHE_FIELD_NAME = "cache"; + + // this should have no effect, as the DNS cache is cleared explicitly in these tests + static { + java.security.Security.setProperty("networkaddress.cache.ttl" , "10000"); + java.security.Security.setProperty("networkaddress.cache.negative.ttl" , "10000"); + } + + @BeforeAll + public static void jniLoad() { + JniHelper.load(); + } + + @BeforeEach + public void setup() { + Core.resetGlobalContext(); + DNSManager.clearCache(); + } + + static class StatefulResource implements Resource { + + int state = 0; + + @Override + public void afterRestore(Context context) { + state += 1; + } + + @Override + public void beforeCheckpoint(Context context) { + state += 2; + } + + int getValue() { + return state; + } + } + + @Test + public void positiveDnsCacheShouldBeEmpty() throws CheckpointException, RestoreException, UnknownHostException, ReflectiveOperationException { + int baselineDNSEntryCount = getDNSEntryCount(); + + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + + String[] hosts = {"www.stackoverflow.com", "www.amazon.com", "www.yahoo.com"}; + for(String singleHost : hosts) { + InetAddress address = InetAddress.getByName(singleHost); + } + // n hosts -> n DNS entries + assertEquals(hosts.length, getDNSEntryCount() - baselineDNSEntryCount); + + // this should call the native static method clearDNSCache + Core.getGlobalContext().beforeCheckpoint(null); + + // cache should be cleared + assertEquals(0, getDNSEntryCount()); + } + + @Test + public void negativeDnsCacheShouldBeEmpty() throws CheckpointException, RestoreException, UnknownHostException, ReflectiveOperationException { + int baselineDNSEntryCount = getDNSEntryCount(); + + StatefulResource resource = new StatefulResource(); + Core.getGlobalContext().register(resource); + + String invalidHost = "not.a.valid.host"; + try { + InetAddress address = InetAddress.getByName(invalidHost); + fail(); + } catch(UnknownHostException uhe) { + // this is actually fine + } + + // 1 host -> 1 DNS entry + assertEquals(1, getDNSEntryCount() - baselineDNSEntryCount); + + // this should the native static method clearDNSCache + Core.getGlobalContext().beforeCheckpoint(null); + + // cache should be cleared + assertEquals(0, getDNSEntryCount()); + } + + // helper functions to access the cache via reflection (see maven-surefire-plugin command args) + protected static Map getDNSCache() throws ReflectiveOperationException { + Class klass = InetAddress.class; + Field acf = klass.getDeclaredField(CACHE_FIELD_NAME); + acf.setAccessible(true); + Object addressCache = acf.get(null); + return (Map) acf.get(addressCache); + } + + protected static int getDNSEntryCount() throws ReflectiveOperationException { + Map cache = getDNSCache(); + return cache.size(); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java index 9e88024b0..5a57e6e03 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/UserFaultTest.java @@ -84,4 +84,44 @@ public void testSuppressedExceptionsAreIncluded() { assertTrue(reportableUserFault.contains("Suppressed: java.lang.RuntimeException: error 2"), "Suppressed error 2 missing in reported UserFault"); } } + + @Test + public void testCircularExceptionReference() { + RuntimeException e1 = new RuntimeException(); + RuntimeException e2 = new RuntimeException(e1); + e1.initCause(e2); + + try { + throw e2; + } catch (Exception e) { + String stackTrace = UserFault.trace(e); + String expectedStackTrace = "java.lang.RuntimeException: java.lang.RuntimeException\n" + + "Caused by: java.lang.RuntimeException\n" + + "Caused by: [CIRCULAR REFERENCE: java.lang.RuntimeException: java.lang.RuntimeException]\n"; + + assertEquals(expectedStackTrace, stackTrace); + } + } + + @Test + public void testCircularSuppressedExceptionReference() { + RuntimeException e1 = new RuntimeException("Primary Exception"); + RuntimeException e2 = new RuntimeException(e1); + RuntimeException e3 = new RuntimeException("Exception with suppressed"); + + e1.addSuppressed(e2); + e3.addSuppressed(e2); + + try { + throw e3; + } catch (Exception e) { + String stackTrace = UserFault.trace(e); + String expectedStackTrace = "java.lang.RuntimeException: Exception with suppressed\n" + + "\tSuppressed: java.lang.RuntimeException: java.lang.RuntimeException: Primary Exception\n" + + "\tCaused by: java.lang.RuntimeException: Primary Exception\n" + + "\t\tSuppressed: [CIRCULAR REFERENCE: java.lang.RuntimeException: java.lang.RuntimeException: Primary Exception]\n"; + + assertEquals(expectedStackTrace, stackTrace); + } + } } diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index 4f8caf39d..cdc27a655 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -44,7 +44,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index fd45aabb8..67dd7617d 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -43,7 +43,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml index 197e97249..04c486a88 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml @@ -38,7 +38,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index 75b816e8b..8222bb41a 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -42,7 +42,7 @@ phases: - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index dd69c8e03..40c79fe98 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.5.0 + 2.5.1 From c0b4f60a8aaa70a8a79c7590202ab24225c6a79d Mon Sep 17 00:00:00 2001 From: Shashank <48707265+ShashankAWS@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:04:13 +0530 Subject: [PATCH 004/173] Added event class MskFirehoseEvent.java for Firehose Lambda transformation when MSK is the source (#490) * Create MskFirehoseEvent.java * Create MSKFirehoseResponse.java --- aws-lambda-java-events/README.md | 2 + .../runtime/events/MSKFirehoseEvent.java | 51 ++++++++++++++++ .../runtime/events/MSKFirehoseResponse.java | 61 +++++++++++++++++++ .../lambda/runtime/tests/EventLoader.java | 4 ++ .../lambda/runtime/tests/EventLoaderTest.java | 13 ++++ .../test/resources/msk_firehose_event.json | 18 ++++++ .../java/example/MSKFirehoseEventHandler.java | 39 ++++++++++++ .../example/MSKFirehoseEventHandlerTest.java | 32 ++++++++++ .../src/test/resources/event.json | 18 ++++++ 9 files changed, 238 insertions(+) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java create mode 100644 aws-lambda-java-tests/src/test/resources/msk_firehose_event.json create mode 100644 samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java create mode 100644 samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java create mode 100644 samples/msk-firehose-event-handler/src/test/resources/event.json diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 8c60b3a23..aaa2a38fe 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -44,6 +44,8 @@ * `KinesisFirehoseEvent` * `LambdaDestinationEvent` * `LexEvent` +* `MSKFirehoseEvent` +* `MSKFirehoseResponse` * `RabbitMQEvent` * `S3BatchEvent` * `S3BatchResponse` diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java new file mode 100644 index 000000000..1af40ce43 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseEvent.java @@ -0,0 +1,51 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.events; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor + +public class MSKFirehoseEvent { + + private String invocationId; + + private String deliveryStreamArn; + + private String sourceMSKArn; + + private String region; + + private List records; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Record { + + private ByteBuffer kafkaRecordValue; + + private String recordId; + + private Long approximateArrivalEpoch; + + private Long approximateArrivalTimestamp; + + private Map mskRecordMetadata; + + } +} diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java new file mode 100644 index 000000000..18b5aa13f --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/MSKFirehoseResponse.java @@ -0,0 +1,61 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.events; + +import java.nio.ByteBuffer; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Response model for Amazon Data Firehose Lambda transformation with MSK as a source. + * [+] Amazon Data Firehose Data Transformation - Data Transformation and Status Model - ... + * OK : Indicates that processing of this item succeeded. + * ProcessingFailed : Indicate that the processing of this item failed. + * Dropped : Indicates that this item should be silently dropped + */ + +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor + +public class MSKFirehoseResponse { + + public enum Result { + + /** + * Indicates that processing of this item succeeded. + */ + Ok, + + /** + * Indicate that the processing of this item failed + */ + ProcessingFailed, + + /** + * Indicates that this item should be silently dropped + */ + Dropped + } + public List records; + + @Data + @NoArgsConstructor + @Builder(setterPrefix = "with") + @AllArgsConstructor + + public static class Record { + public String recordId; + public Result result; + public ByteBuffer kafkaRecordValue; + + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index aa600749c..601d2f3fb 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -89,6 +89,10 @@ public static LexEvent loadLexEvent(String filename) { return loadEvent(filename, LexEvent.class); } + public static MSKFirehoseEvent loadMSKFirehoseEvent(String filename) { + return loadEvent(filename, MSKFirehoseEvent.class); + } + public static S3Event loadS3Event(String filename) { return loadEvent(filename, S3Event.class); } diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 1c9d17e16..12dc436c7 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -118,6 +118,19 @@ public void testLoadKinesisFirehoseEvent() { assertThat(event.getRecords().get(0).getData().array()).asString().isEqualTo("Hello, this is a test 123."); } + @Test + public void testLoadMSKFirehoseEvent() { + MSKFirehoseEvent event = EventLoader.loadMSKFirehoseEvent("msk_firehose_event.json"); + + assertThat(event).isNotNull(); + assertThat(event.getSourceMSKArn()).isEqualTo("arn:aws:kafka:EXAMPLE"); + assertThat(event.getDeliveryStreamArn()).isEqualTo("arn:aws:firehose:EXAMPLE"); + assertThat(event.getRecords()).hasSize(1); + assertThat(event.getRecords().get(0).getKafkaRecordValue().array()).asString().isEqualTo("{\"Name\":\"Hello World\"}"); + assertThat(event.getRecords().get(0).getApproximateArrivalTimestamp()).asString().isEqualTo("1716369573887"); + assertThat(event.getRecords().get(0).getMskRecordMetadata()).asString().isEqualTo("{offset=0, partitionId=1, approximateArrivalTimestamp=1716369573887}"); + } + @Test public void testLoadS3Event() { S3Event event = EventLoader.loadS3Event("s3_event.json"); diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json new file mode 100644 index 000000000..6b839912d --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "arn:aws:kafka:EXAMPLE", + "deliveryStreamArn": "arn:aws:firehose:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": 1716369573887 + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} diff --git a/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java b/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java new file mode 100644 index 000000000..f5e513496 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/main/java/example/MSKFirehoseEventHandler.java @@ -0,0 +1,39 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package example; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseResponse; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; +import org.json.JSONObject; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * A sample MSKFirehoseEvent handler + * For more information see the developer guide - ... + */ +public class MSKFirehoseEventHandler implements RequestHandler { + + @Override + public MSKFirehoseResponse handleRequest(MSKFirehoseEvent MSKFirehoseEvent, Context context) { + List records = new ArrayList<>(); + + for (MSKFirehoseEvent.Record record : MSKFirehoseEvent.getRecords()) { + String recordData = new String(record.getKafkaRecordValue().array()); + // Your business logic + JSONObject jsonObject = new JSONObject(recordData); + records.add(new MSKFirehoseResponse.Record(record.getRecordId(), MSKFirehoseResponse.Result.Ok, encode(jsonObject.toString()))); + } + return new MSKFirehoseResponse(records); + } + private ByteBuffer encode(String content) { + return ByteBuffer.wrap(content.getBytes()); + } +} diff --git a/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java b/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java new file mode 100644 index 000000000..77223e516 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/test/java/example/MSKFirehoseEventHandlerTest.java @@ -0,0 +1,32 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package example; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class MSKFirehoseEventHandlerTest { + + private Context context; // intentionally null as it's not used in the test + + @ParameterizedTest + @Event(value = "event.json", type = MSKFirehoseEvent.class) + public void testEventHandler(MSKFirehoseEvent event) { + MSKFirehoseEventHandler Sample = new MSKFirehoseEventHandler(); + MSKFirehoseResponse response = Sample.handleRequest(event, context); + + String expectedString = "{\"Name\":\"Hello World\"}"; + MSKFirehoseResponse.Record firstRecord = response.getRecords().get(0); + Assertions.assertEquals(expectedString, UTF_8.decode(firstRecord.getKafkaRecordValue()).toString()); + Assertions.assertEquals(MSKFirehoseResponse.Result.Ok, firstRecord.getResult()); + } +} diff --git a/samples/msk-firehose-event-handler/src/test/resources/event.json b/samples/msk-firehose-event-handler/src/test/resources/event.json new file mode 100644 index 000000000..91c4b4203 --- /dev/null +++ b/samples/msk-firehose-event-handler/src/test/resources/event.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "", + "deliveryStreamArn": "", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": 1716369573887 + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} From aa9686fcbbf8fe032fc628c73050da16e9055732 Mon Sep 17 00:00:00 2001 From: Mark Sailes Date: Thu, 11 Jul 2024 11:56:44 +0100 Subject: [PATCH 005/173] CloudWatch alarm events --- aws-lambda-java-events/README.md | 2 + aws-lambda-java-events/pom.xml | 2 + .../events/CloudWatchCompositeAlarmEvent.java | 70 +++++++++ .../events/CloudWatchMetricAlarmEvent.java | 99 +++++++++++++ .../lambda/runtime/tests/EventLoader.java | 8 + .../lambda/runtime/tests/EventLoaderTest.java | 138 +++++++++++++++++- .../resources/cloudwatch_composite_alarm.json | 30 ++++ .../resources/cloudwatch_metric_alarm.json | 42 ++++++ 8 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java create mode 100644 aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json create mode 100644 aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index aaa2a38fe..92bea0ea6 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -16,7 +16,9 @@ * `AppSyncLambdaAuthorizerResponse` * `CloudFormationCustomResourceEvent` * `CloudFrontEvent` +* `CloudWatchCompositeAlarmEvent` * `CloudWatchLogsEvent` +* `CloudWatchMetricAlarmEvent` * `CodeCommitEvent` * `CognitoEvent` * `CognitoUserPoolCreateAuthChallengeEvent` diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 2c7442c06..4abea0f87 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -35,6 +35,8 @@ 1.8 1.8 1.18.22 + UTF-8 + UTF-8 diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java new file mode 100644 index 000000000..d4090b55b --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchCompositeAlarmEvent.java @@ -0,0 +1,70 @@ +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Represents an CloudWatch Composite Alarm event. This event occurs when a composite alarm is triggered. + * + * @see Using Amazon CloudWatch alarms + */ +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor +public class CloudWatchCompositeAlarmEvent { + private String source; + private String alarmArn; + private String accountId; + private String time; + private String region; + private AlarmData alarmData; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmData { + private String alarmName; + private State state; + private PreviousState previousState; + private Configuration configuration; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class State { + private String value; + private String reason; + private String reasonData; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class PreviousState { + private String value; + private String reason; + private String reasonData; + private String timestamp; + private String actionsSuppressedBy; + private String actionsSuppressedReason; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Configuration { + private String alarmRule; + private String actionsSuppressor; + private Integer actionsSuppressorWaitPeriod; + private Integer actionsSuppressorExtensionPeriod; + } +} diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java new file mode 100644 index 000000000..2b5f503c3 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/CloudWatchMetricAlarmEvent.java @@ -0,0 +1,99 @@ +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Represents an CloudWatch Metric Alarm event. This event occurs when a metric alarm is triggered. + * + * @see Using Amazon CloudWatch alarms + */ +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor +public class CloudWatchMetricAlarmEvent { + private String source; + private String alarmArn; + private String accountId; + private String time; + private String region; + private AlarmData alarmData; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class AlarmData { + private String alarmName; + private State state; + private PreviousState previousState; + private Configuration configuration; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class State { + private String value; + private String reason; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class PreviousState { + private String value; + private String reason; + private String reasonData; + private String timestamp; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Configuration { + private String description; + private List metrics; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Metric { + private String id; + private MetricStat metricStat; + private Boolean returnData; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class MetricStat { + private MetricDetail metric; + private Integer period; + private String stat; + private String unit; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class MetricDetail { + private String namespace; + private String name; + private Map dimensions; + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index 601d2f3fb..778122673 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -45,10 +45,18 @@ public static CloudFrontEvent loadCloudFrontEvent(String filename) { return loadEvent(filename, CloudFrontEvent.class); } + public static CloudWatchCompositeAlarmEvent loadCloudWatchCompositeAlarmEvent(String filename) { + return loadEvent(filename, CloudWatchCompositeAlarmEvent.class); + } + public static CloudWatchLogsEvent loadCloudWatchLogsEvent(String filename) { return loadEvent(filename, CloudWatchLogsEvent.class); } + public static CloudWatchMetricAlarmEvent loadCloudWatchMetricAlarmEvent(String filename) { + return loadEvent(filename, CloudWatchMetricAlarmEvent.class); + } + public static CodeCommitEvent loadCodeCommitEvent(String filename) { return loadEvent(filename, CodeCommitEvent.class); } diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 12dc436c7..4aa920f8c 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -1,6 +1,38 @@ /* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ package com.amazonaws.services.lambda.runtime.tests; +import com.amazonaws.services.lambda.runtime.events.APIGatewayCustomAuthorizerEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2CustomAuthorizerEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; +import com.amazonaws.services.lambda.runtime.events.ActiveMQEvent; +import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; +import com.amazonaws.services.lambda.runtime.events.CloudFrontEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.AlarmData; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.Configuration; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.PreviousState; +import com.amazonaws.services.lambda.runtime.events.CloudWatchCompositeAlarmEvent.State; +import com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent; +import com.amazonaws.services.lambda.runtime.events.CloudWatchMetricAlarmEvent; +import com.amazonaws.services.lambda.runtime.events.CodeCommitEvent; +import com.amazonaws.services.lambda.runtime.events.CognitoUserPoolPreTokenGenerationEventV2; +import com.amazonaws.services.lambda.runtime.events.ConfigEvent; +import com.amazonaws.services.lambda.runtime.events.ConnectEvent; +import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; +import com.amazonaws.services.lambda.runtime.events.KafkaEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent; +import com.amazonaws.services.lambda.runtime.events.LexEvent; +import com.amazonaws.services.lambda.runtime.events.MSKFirehoseEvent; +import com.amazonaws.services.lambda.runtime.events.RabbitMQEvent; +import com.amazonaws.services.lambda.runtime.events.S3Event; +import com.amazonaws.services.lambda.runtime.events.SNSEvent; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.Record; import com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord; @@ -15,8 +47,6 @@ import static java.time.Instant.ofEpochSecond; import static org.assertj.core.api.Assertions.*; -import com.amazonaws.services.lambda.runtime.events.*; - public class EventLoaderTest { @Test @@ -389,4 +419,108 @@ public void testLoadCognitoUserPoolPreTokenGenerationEventV2() { String[] requestScopes = request.getScopes(); assertThat("aws.cognito.signin.user.admin").isEqualTo(requestScopes[0]); } + + @Test + public void testCloudWatchCompositeAlarmEvent() { + CloudWatchCompositeAlarmEvent event = EventLoader.loadCloudWatchCompositeAlarmEvent("cloudwatch_composite_alarm.json"); + assertThat(event).isNotNull(); + assertThat(event) + .returns("aws.cloudwatch", from(CloudWatchCompositeAlarmEvent::getSource)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", from(CloudWatchCompositeAlarmEvent::getAlarmArn)) + .returns("111122223333", from(CloudWatchCompositeAlarmEvent::getAccountId)) + .returns("2023-08-04T12:56:46.138+0000", from(CloudWatchCompositeAlarmEvent::getTime)) + .returns("us-east-1", from(CloudWatchCompositeAlarmEvent::getRegion)); + + AlarmData alarmData = event.getAlarmData(); + assertThat(alarmData).isNotNull(); + assertThat(alarmData) + .returns("CompositeDemo.Main", from(AlarmData::getAlarmName)); + + State state = alarmData.getState(); + assertThat(state).isNotNull(); + assertThat(state) + .returns("ALARM", from(State::getValue)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", from(State::getReason)) + .returns("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", from(State::getReasonData)) + .returns("2023-08-04T12:56:46.138+0000", from(State::getTimestamp)); + + PreviousState previousState = alarmData.getPreviousState(); + assertThat(previousState).isNotNull(); + assertThat(previousState) + .returns("ALARM", from(PreviousState::getValue)) + .returns("arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", from(PreviousState::getReason)) + .returns("{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", from(PreviousState::getReasonData)) + .returns("2023-08-04T12:54:46.138+0000", from(PreviousState::getTimestamp)) + .returns("WaitPeriod", from(PreviousState::getActionsSuppressedBy)) + .returns("Actions suppressed by WaitPeriod", from(PreviousState::getActionsSuppressedReason)); + + Configuration configuration = alarmData.getConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration) + .returns("ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", from(Configuration::getAlarmRule)) + .returns("CompositeDemo.ActionsSuppressor", from(Configuration::getActionsSuppressor)) + .returns(120, from(Configuration::getActionsSuppressorWaitPeriod)) + .returns(180, from(Configuration::getActionsSuppressorExtensionPeriod)); + } + + @Test + public void testCloudWatchMetricAlarmEvent() { + CloudWatchMetricAlarmEvent event = EventLoader.loadCloudWatchMetricAlarmEvent("cloudwatch_metric_alarm.json"); + assertThat(event).isNotNull(); + assertThat(event) + .returns("aws.cloudwatch", from(CloudWatchMetricAlarmEvent::getSource)) + .returns("arn:aws:cloudwatch:us-east-1:444455556666:alarm:lambda-demo-metric-alarm", from(CloudWatchMetricAlarmEvent::getAlarmArn)) + .returns("444455556666", from(CloudWatchMetricAlarmEvent::getAccountId)) + .returns("2023-08-04T12:36:15.490+0000", from(CloudWatchMetricAlarmEvent::getTime)) + .returns("us-east-1", from(CloudWatchMetricAlarmEvent::getRegion)); + + CloudWatchMetricAlarmEvent.AlarmData alarmData = event.getAlarmData(); + assertThat(alarmData).isNotNull(); + assertThat(alarmData) + .returns("lambda-demo-metric-alarm", from(CloudWatchMetricAlarmEvent.AlarmData::getAlarmName)); + + CloudWatchMetricAlarmEvent.State state = alarmData.getState(); + assertThat(state).isNotNull(); + assertThat(state) + .returns("ALARM", from(CloudWatchMetricAlarmEvent.State::getValue)) + .returns("test", from(CloudWatchMetricAlarmEvent.State::getReason)) + .returns("2023-08-04T12:36:15.490+0000", from(CloudWatchMetricAlarmEvent.State::getTimestamp)); + + CloudWatchMetricAlarmEvent.PreviousState previousState = alarmData.getPreviousState(); + assertThat(previousState).isNotNull(); + assertThat(previousState) + .returns("INSUFFICIENT_DATA", from(CloudWatchMetricAlarmEvent.PreviousState::getValue)) + .returns("Insufficient Data: 5 datapoints were unknown.", from(CloudWatchMetricAlarmEvent.PreviousState::getReason)) + .returns("{\"version\":\"1.0\",\"queryDate\":\"2023-08-04T12:31:29.591+0000\",\"statistic\":\"Average\",\"period\":60,\"recentDatapoints\":[],\"threshold\":5.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2023-08-04T12:30:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:29:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:28:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:27:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:26:00.000+0000\"}]}", from(CloudWatchMetricAlarmEvent.PreviousState::getReasonData)) + .returns("2023-08-04T12:31:29.595+0000", from(CloudWatchMetricAlarmEvent.PreviousState::getTimestamp)); + + CloudWatchMetricAlarmEvent.Configuration configuration = alarmData.getConfiguration(); + assertThat(configuration).isNotNull(); + assertThat(configuration) + .returns("Metric Alarm to test Lambda actions", from(CloudWatchMetricAlarmEvent.Configuration::getDescription)); + + List metrics = configuration.getMetrics(); + assertThat(metrics).hasSize(1); + CloudWatchMetricAlarmEvent.Metric metric = metrics.get(0); + assertThat(metric) + .returns("1234e046-06f0-a3da-9534-EXAMPLEe4c", from(CloudWatchMetricAlarmEvent.Metric::getId)); + + CloudWatchMetricAlarmEvent.MetricStat metricStat = metric.getMetricStat(); + assertThat(metricStat).isNotNull(); + assertThat(metricStat) + .returns(60, from(CloudWatchMetricAlarmEvent.MetricStat::getPeriod)) + .returns("Average", from(CloudWatchMetricAlarmEvent.MetricStat::getStat)) + .returns("Percent", from(CloudWatchMetricAlarmEvent.MetricStat::getUnit)); + + CloudWatchMetricAlarmEvent.MetricDetail metricDetail = metricStat.getMetric(); + assertThat(metricDetail).isNotNull(); + assertThat(metricDetail) + .returns("AWS/Logs", from(CloudWatchMetricAlarmEvent.MetricDetail::getNamespace)) + .returns("CallCount", from(CloudWatchMetricAlarmEvent.MetricDetail::getName)); + + Map dimensions = metricDetail.getDimensions(); + assertThat(dimensions).isNotEmpty().hasSize(1); + assertThat(dimensions) + .contains(entry("InstanceId", "i-12345678")); + } } diff --git a/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json b/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json new file mode 100644 index 000000000..353d470ae --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cloudwatch_composite_alarm.json @@ -0,0 +1,30 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:SuppressionDemo.Main", + "accountId": "111122223333", + "time": "2023-08-04T12:56:46.138+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "CompositeDemo.Main", + "state": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12:56:46.138+0000" + }, + "previousState": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12:54:46 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12:54:46.138+0000", + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod" + }, + "configuration": { + "alarmRule": "ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", + "actionsSuppressor": "CompositeDemo.ActionsSuppressor", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json b/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json new file mode 100644 index 000000000..61b4187b5 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cloudwatch_metric_alarm.json @@ -0,0 +1,42 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1:444455556666:alarm:lambda-demo-metric-alarm", + "accountId": "444455556666", + "time": "2023-08-04T12:36:15.490+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "lambda-demo-metric-alarm", + "state": { + "value": "ALARM", + "reason": "test", + "timestamp": "2023-08-04T12:36:15.490+0000" + }, + "previousState": { + "value": "INSUFFICIENT_DATA", + "reason": "Insufficient Data: 5 datapoints were unknown.", + "reasonData": "{\"version\":\"1.0\",\"queryDate\":\"2023-08-04T12:31:29.591+0000\",\"statistic\":\"Average\",\"period\":60,\"recentDatapoints\":[],\"threshold\":5.0,\"evaluatedDatapoints\":[{\"timestamp\":\"2023-08-04T12:30:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:29:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:28:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:27:00.000+0000\"},{\"timestamp\":\"2023-08-04T12:26:00.000+0000\"}]}", + "timestamp": "2023-08-04T12:31:29.595+0000" + }, + "configuration": { + "description": "Metric Alarm to test Lambda actions", + "metrics": [ + { + "id": "1234e046-06f0-a3da-9534-EXAMPLEe4c", + "metricStat": { + "metric": { + "namespace": "AWS/Logs", + "name": "CallCount", + "dimensions": { + "InstanceId": "i-12345678" + } + }, + "period": 60, + "stat": "Average", + "unit": "Percent" + }, + "returnData": true + } + ] + } + } +} \ No newline at end of file From 791486fd912420bbf01bf4bbf1418baa6ffced04 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 11 Jul 2024 15:30:45 +0100 Subject: [PATCH 006/173] bump: aws-lambda-java-events from 3.11.6 to 3.12.0 --- README.md | 2 +- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 94f842fb3..cc560dcb7 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.11.6 + 3.12.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 92bea0ea6..53377ecc4 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -73,7 +73,7 @@ com.amazonaws aws-lambda-java-events - 3.11.6 + 3.12.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index a19bd260a..12e1e0bb1 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### July 11, 2024 +`3.12.0`: +- Added the object representations of the CloudWatch alarms([#493](https://github.com/aws/aws-lambda-java-libs/pull/493)) + ### June 11, 2024 `3.11.6`: - Add the V2 version of the pre token generation event([#465](https://github.com/aws/aws-lambda-java-libs/pull/465)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 4abea0f87..909b3997c 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.11.6 + 3.12.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index b77032b73..2ecbd7a9d 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.11.6 + 3.12.0 org.junit.jupiter diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 1dc96d3d5..145337f53 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.11.6 + 3.12.0 From 2ef83fe98fec3cb82b0bd529d6483ea2eecc71b1 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 11 Jul 2024 15:37:05 +0100 Subject: [PATCH 007/173] bump: aws-lambda-java-events from 3.11.6 to 3.12.0 --- aws-lambda-java-events/RELEASE.CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 12e1e0bb1..bbb9505e8 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,6 +1,7 @@ ### July 11, 2024 `3.12.0`: - Added the object representations of the CloudWatch alarms([#493](https://github.com/aws/aws-lambda-java-libs/pull/493)) +- Added event class MskFirehoseEvent.java for Firehose Lambda transformation when MSK is the source([#490](https://github.com/aws/aws-lambda-java-libs/pull/490)) ### June 11, 2024 `3.11.6`: From a626ed390ce6ce619bb2a025b8d869f5063354be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:21:45 +0100 Subject: [PATCH 008/173] Bump docker/setup-buildx-action from 2 to 3 (#488) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 27b22fd78..27c7dfec8 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -39,7 +39,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: install: true diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index b935f4f7d..0d4aa9b21 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -44,7 +44,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: install: true From 70467ba5af66d4e32c3eb92f9f1988b15552b48a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:13:39 +0100 Subject: [PATCH 009/173] Bump actions/checkout from 3 to 4 (#489) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/repo-sync.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 39ff12914..267d901c9 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 05a870add..66f6b2bfe 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 634d803ea..04ab53a50 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index ab86f200f..7ae54cbe1 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index baa6052fc..c24c48d72 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index bf17ed2a6..a28bca886 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index c667bcebd..25f05029a 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -16,7 +16,7 @@ jobs: env: IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ env.IS_CONFIGURED == 'true' }} - uses: repo-sync/github-sync@v2 name: Sync repo to branch diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 27c7dfec8..8dd3b8179 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -27,7 +27,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 0d4aa9b21..6cf510889 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -15,7 +15,7 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -32,7 +32,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index cd6655494..cdf923eae 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: From c5e2ee3f0346a97a894119f4b296c1b666fb623e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 29 Jul 2024 11:47:35 +0100 Subject: [PATCH 010/173] fix: make initializeClient a pure function (#498) * fix: make initializeClient a pure function * fix: make initializeClient a pure function --- .../api/client/runtimeapi/LambdaRuntimeApiClientImpl.java | 2 +- .../lambda/runtime/api/client/runtimeapi/NativeClient.java | 6 +++--- ...es_lambda_runtime_api_client_runtimeapi_NativeClient.cpp | 4 ++-- ...ices_lambda_runtime_api_client_runtimeapi_NativeClient.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java index 83ac0b39a..a62e0a692 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java @@ -38,7 +38,7 @@ public LambdaRuntimeApiClientImpl(String hostnameAndPort) { Objects.requireNonNull(hostnameAndPort, "hostnameAndPort cannot be null"); this.baseUrl = "http://" + hostnameAndPort; this.invocationEndpoint = this.baseUrl + "/2018-06-01/runtime/invocation/"; - NativeClient.init(); + NativeClient.init(hostnameAndPort); } @Override diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java index 10e6bafd4..3929e1147 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java @@ -14,12 +14,12 @@ * interactions with the Runtime API. */ class NativeClient { - static void init() { + static void init(String awsLambdaRuntimeApi) { JniHelper.load(); - initializeClient(USER_AGENT.getBytes()); + initializeClient(USER_AGENT.getBytes(), awsLambdaRuntimeApi.getBytes()); } - static native void initializeClient(byte[] userAgent); + static native void initializeClient(byte[] userAgent, byte[] awsLambdaRuntimeApi); static native InvocationRequest next(); diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index db71b8194..7fe47aa4d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -69,9 +69,9 @@ static std::string toNativeString(JNIEnv *env, jbyteArray jArray) { return nativeString; } -JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_initializeClient(JNIEnv *env, jobject thisObject, jbyteArray userAgent) { +JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_initializeClient(JNIEnv *env, jobject thisObject, jbyteArray userAgent, jbyteArray awsLambdaRuntimeApi) { std::string user_agent = toNativeString(env, userAgent); - std::string endpoint(getenv("AWS_LAMBDA_RUNTIME_API")); + std::string endpoint = toNativeString(env, awsLambdaRuntimeApi); CLIENT = new aws::lambda_runtime::runtime(endpoint, user_agent); } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h index 27c636117..7219109b0 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h @@ -11,7 +11,7 @@ extern "C" { #endif JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_initializeClient - (JNIEnv *, jobject, jbyteArray); + (JNIEnv *, jobject, jbyteArray, jbyteArray); JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_next (JNIEnv *, jobject); From a36e04148ac2cac04d1f16feec3c6ae67254c34d Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 29 Jul 2024 12:04:59 +0100 Subject: [PATCH 011/173] misc: add Maven badges to README (#499) --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index cc560dcb7..4aabef59c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ For information on how to optimize your functions watch the re:Invent talk [Opti ## Core Java Lambda interfaces - aws-lambda-java-core +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-core.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-core) + This package defines the Lambda [Context](http://docs.aws.amazon.com/lambda/latest/dg/java-context-object.html) object as well as [interfaces](http://docs.aws.amazon.com/lambda/latest/dg/java-handler-using-predefined-interfaces.html) that Lambda accepts. @@ -47,6 +49,8 @@ public class HandlerStream implements RequestStreamHandler { ## Java objects of Lambda event sources - aws-lambda-java-events +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-events.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-events) + This package defines [event sources](http://docs.aws.amazon.com/lambda/latest/dg/intro-invocation-modes.html) that Lambda natively accepts. See the [documentation](aws-lambda-java-events/README.md) for a list of currently supported event sources. Using this library you can have Java objects which represent event sources. @@ -77,6 +81,8 @@ public class SqsHandler implements RequestHandler { ## Java Lambda JUnit Support - aws-lambda-java-tests +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-tests.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-tests) + This package provides utils to ease Lambda Java testing. It uses the same Lambda serialisation logic and `aws-lambda-java-events` to inject events in your JUnit tests. - [Release Notes](aws-lambda-java-tests/RELEASE.CHANGELOG.md) @@ -100,6 +106,8 @@ public void testInjectSQSEvent(SQSEvent event) { ## aws-lambda-java-events-sdk-transformer +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-events-sdk-transformer.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-events-sdk-transformer) + This package provides helper classes/methods to use alongside `aws-lambda-java-events` in order to transform Lambda input event model objects into SDK-compatible output model objects. See the [documentation](aws-lambda-java-events-sdk-transformer/README.md) for more information. @@ -116,6 +124,8 @@ See the [documentation](aws-lambda-java-events-sdk-transformer/README.md) for mo ## Java Lambda Log4J2 support - aws-lambda-java-log4j2 +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-log4j2.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-log4j2) + This package defines the Lambda adapter to use with Log4J version 2. See the [README](aws-lambda-java-log4j2/README.md) or the [official documentation](http://docs.aws.amazon.com/lambda/latest/dg/java-logging.html#java-wt-logging-using-log4j) for information on how to use the adapter. @@ -130,6 +140,7 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio ``` ## Java implementation of the Runtime Interface Client API - aws-lambda-java-runtime-interface-client +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-runtime-interface-client.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-runtime-interface-client) This package defines the Lambda Java Runtime Interface Client package, a Lambda Runtime component that starts the runtime and interacts with the Runtime API - i.e., it calls the API for invocation events, starts the function code, calls the API to return the response. The purpose of this package is to allow developers to deploy their applications in Lambda under the form of Container Images. See the [README](aws-lambda-java-runtime-interface-client/README.md) for information on how to use the library. @@ -146,6 +157,8 @@ The purpose of this package is to allow developers to deploy their applications ## Java Lambda provided serialization support - aws-lambda-java-serialization +[![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-serialization.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-serialization) + This package defines the Lambda serialization logic using in the `aws-lambda-java-runtime-client` library. It has no current standalone usage. - [Release Notes](aws-lambda-java-serialization/RELEASE.CHANGELOG.md) From aa399f58791eaf0273c3780a7b5f477806a55731 Mon Sep 17 00:00:00 2001 From: Martin Merfort <395822+mmerfort@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:13:57 +0200 Subject: [PATCH 012/173] Add S3BatchEventV2 (#496) * Add S3BatchEventV2 class * Add test for S3BatchEventV2 --------- Co-authored-by: Martin Merfort Co-authored-by: Alexander Smirnov <75367056+smirnoal@users.noreply.github.com> --- .../lambda/runtime/events/S3BatchEventV2.java | 50 +++++++++++++++++++ .../runtime/events/S3BatchResponse.java | 8 ++- .../lambda/runtime/tests/EventLoader.java | 4 ++ .../runtime/tests/S3BatchEventV2Test.java | 20 ++++++++ .../src/test/resources/s3_batch_event_v2.json | 21 ++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchEventV2.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/S3BatchEventV2Test.java create mode 100644 aws-lambda-java-tests/src/test/resources/s3_batch_event_v2.json diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchEventV2.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchEventV2.java new file mode 100644 index 000000000..92b8722d7 --- /dev/null +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchEventV2.java @@ -0,0 +1,50 @@ +package com.amazonaws.services.lambda.runtime.events; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * Event to represent the payload which is sent to Lambda by S3 Batch to perform a custom + * action when using invocation schema version 2.0. + * + * https://docs.aws.amazon.com/AmazonS3/latest/dev/batch-ops-invoke-lambda.html + */ + +@Data +@Builder(setterPrefix = "with") +@NoArgsConstructor +@AllArgsConstructor +public class S3BatchEventV2 { + + private String invocationSchemaVersion; + private String invocationId; + private Job job; + private List tasks; + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Job { + + private String id; + private Map userArguments; + } + + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Task { + + private String taskId; + private String s3Key; + private String s3VersionId; + private String s3BucketName; + } +} diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchResponse.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchResponse.java index 4fdd12732..d584a31dd 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchResponse.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/S3BatchResponse.java @@ -62,4 +62,10 @@ public static S3BatchResponseBuilder fromS3BatchEvent(S3BatchEvent s3BatchEvent) .withInvocationId(s3BatchEvent.getInvocationId()) .withInvocationSchemaVersion(s3BatchEvent.getInvocationSchemaVersion()); } -} \ No newline at end of file + + public static S3BatchResponseBuilder fromS3BatchEvent(S3BatchEventV2 s3BatchEvent) { + return S3BatchResponse.builder() + .withInvocationId(s3BatchEvent.getInvocationId()) + .withInvocationSchemaVersion(s3BatchEvent.getInvocationSchemaVersion()); + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java index 778122673..0c5d66206 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/EventLoader.java @@ -105,6 +105,10 @@ public static S3Event loadS3Event(String filename) { return loadEvent(filename, S3Event.class); } + public static S3BatchEventV2 loadS3BatchEventV2(String filename) { + return loadEvent(filename, S3BatchEventV2.class); + } + public static SecretsManagerRotationEvent loadSecretsManagerRotationEvent(String filename) { return loadEvent(filename, SecretsManagerRotationEvent.class); } diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/S3BatchEventV2Test.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/S3BatchEventV2Test.java new file mode 100644 index 000000000..2c9913dc4 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/S3BatchEventV2Test.java @@ -0,0 +1,20 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.S3BatchEventV2; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +public class S3BatchEventV2Test { + + @Test + public void testS3BatchEventV2() { + S3BatchEventV2 event = EventLoader.loadS3BatchEventV2("s3_batch_event_v2.json"); + assertThat(event).isNotNull(); + assertThat(event.getInvocationId()).isEqualTo("Jr3s8KZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZzatx7Ruy"); + assertThat(event.getJob()).isNotNull(); + assertThat(event.getJob().getUserArguments().get("MyDestinationBucket")).isEqualTo("destination-directory-bucket-name"); + assertThat(event.getTasks()).hasSize(1); + assertThat(event.getTasks().get(0).getS3Key()).isEqualTo("s3objectkey"); + } +} diff --git a/aws-lambda-java-tests/src/test/resources/s3_batch_event_v2.json b/aws-lambda-java-tests/src/test/resources/s3_batch_event_v2.json new file mode 100644 index 000000000..4cdbacaaa --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/s3_batch_event_v2.json @@ -0,0 +1,21 @@ +{ + "invocationSchemaVersion": "2.0", + "invocationId": "Jr3s8KZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZzatx7Ruy", + "job": { + "id": "ry77cd60-61f6-4a2b-8a21-d07600c874gf", + "userArguments": { + "MyDestinationBucket": "destination-directory-bucket-name", + "MyDestinationBucketRegion": "us-east-1", + "MyDestinationPrefix": "copied/", + "MyDestinationObjectKeySuffix": "_new_suffix" + } + }, + "tasks": [ + { + "taskId": "y5R3a2lkZ29lc2hlurcS", + "s3Key": "s3objectkey", + "s3VersionId": null, + "s3Bucket": "source-directory-bucket-name" + } + ] +} From bf708eef3bcbfd746e3adb7473cf35a67f3cecf5 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 29 Jul 2024 14:08:41 +0100 Subject: [PATCH 013/173] bump: aws-lambda-java-events from 3.12.0 to 3.13.0 (#500) * bump: aws-lambda-java-events from 3.12.0 to 3.13.0 --- README.md | 2 +- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4aabef59c..de00e92ca 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.12.0 + 3.13.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 53377ecc4..a37847848 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -73,7 +73,7 @@ com.amazonaws aws-lambda-java-events - 3.12.0 + 3.13.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index bbb9505e8..ff2160e6e 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### July 29, 2024 +`3.13.0`: +- Add S3BatchEventV2 ([#496](https://github.com/aws/aws-lambda-java-libs/pull/496)) + ### July 11, 2024 `3.12.0`: - Added the object representations of the CloudWatch alarms([#493](https://github.com/aws/aws-lambda-java-libs/pull/493)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 909b3997c..e13d5bb27 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.12.0 + 3.13.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 2ecbd7a9d..233a0f0fc 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.12.0 + 3.13.0 org.junit.jupiter diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 145337f53..dcea81c5b 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.12.0 + 3.13.0 From ff5d9e0c58c97be9e9451a5669ecc9049fe51743 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 30 Jul 2024 13:43:00 +0100 Subject: [PATCH 014/173] misc: add linter (#497) * misc: add linter * misc: add linter --- .../build-tools/checkstyle.xml | 115 ++ .../pom.xml | 21 + .../lambda/crac/CheckpointException.java | 6 +- .../services/lambda/crac/Context.java | 5 +- .../services/lambda/crac/ContextImpl.java | 25 +- .../amazonaws/services/lambda/crac/Core.java | 9 +- .../services/lambda/crac/DNSManager.java | 2 +- .../services/lambda/crac/Resource.java | 5 +- .../lambda/crac/RestoreException.java | 6 +- .../lambda/runtime/api/client/AWSLambda.java | 8 +- .../runtime/api/client/ClasspathLoader.java | 5 +- .../api/client/CustomerClassLoader.java | 8 +- .../api/client/EventHandlerLoader.java | 1286 +++++++++-------- .../runtime/api/client/HandlerInfo.java | 15 +- .../runtime/api/client/LambdaEnvironment.java | 15 +- .../api/client/LambdaRequestHandler.java | 22 +- .../api/client/PojoSerializerLoader.java | 8 +- .../ReservedRuntimeEnvironmentVariables.java | 18 +- ...TooManyServiceProvidersFoundException.java | 7 +- .../lambda/runtime/api/client/UserFault.java | 10 +- .../api/client/api/LambdaClientContext.java | 6 +- .../client/api/LambdaClientContextClient.java | 5 +- .../api/client/api/LambdaCognitoIdentity.java | 5 +- .../runtime/api/client/api/LambdaContext.java | 5 +- .../client/logging/AbstractLambdaLogger.java | 12 +- .../runtime/api/client/logging/FrameType.java | 21 +- .../logging/FramedTelemetryLogSink.java | 12 +- .../api/client/logging/JsonLogFormatter.java | 16 +- .../client/logging/LambdaContextLogger.java | 12 +- .../api/client/logging/LogFiltering.java | 5 +- .../api/client/logging/LogFormatter.java | 7 +- .../runtime/api/client/logging/LogSink.java | 10 +- .../api/client/logging/StdOutLogSink.java | 10 +- .../client/logging/StructuredLogMessage.java | 5 +- .../api/client/logging/TextLogFormatter.java | 26 +- .../api/client/runtimeapi/DtoSerializers.java | 21 +- .../api/client/runtimeapi/JniHelper.java | 14 +- .../runtimeapi/LambdaRuntimeApiClient.java | 1 - .../LambdaRuntimeApiClientImpl.java | 18 +- .../api/client/runtimeapi/NativeClient.java | 2 - .../converters/LambdaErrorConverter.java | 4 +- .../converters/XRayErrorCauseConverter.java | 13 +- .../runtime/api/client/util/EnvReader.java | 5 +- .../api/client/util/LambdaOutputStream.java | 7 +- .../runtime/api/client/util/UnsafeUtil.java | 8 +- .../services/lambda/crac/ContextImplTest.java | 1 + 46 files changed, 1031 insertions(+), 816 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/build-tools/checkstyle.xml diff --git a/aws-lambda-java-runtime-interface-client/build-tools/checkstyle.xml b/aws-lambda-java-runtime-interface-client/build-tools/checkstyle.xml new file mode 100644 index 000000000..263834dc4 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/build-tools/checkstyle.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a4f022a48..24e73a8de 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -38,6 +38,7 @@ 2.4 3.1.1 5.9.2 + 3.4.0 + Icon-Architecture/64/Arch_AWS-Lambda_64 + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png b/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png new file mode 100644 index 0000000000000000000000000000000000000000..81ae8cba3923ad19695e673fb5096108c133580c GIT binary patch literal 543704 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

+CThcE!0472oT0=>inwKGYVK% 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

4FBmYr;@u;Etx;8t!Rj}G}g+>b9NY!|nM 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

eagPHI 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 z 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 v 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 QbYuVmPgg&ebxsLQ0N%lq`2YX_ literal 0 HcmV?d00001 diff --git a/aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png b/aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..26d11c310f21fcc029e81777149b61fefe395ae2 GIT binary patch literal 721242 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

&CK1eIK+_&#tz9IeKhu&o0hl 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

h>k@$@r{j)q$i<_Y2w&gkl6 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

^(ffCa42|BAAsWYM-#opKJDbN8SnwU~1cgZyVi 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

    HZhb>&>r|#4* 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 Tqv>}_eeoUzQXc8@O678w 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%0DT0MWW*W~sai#$?Hc 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 literal 0 HcmV?d00001 diff --git a/aws-lambda-java-profiler/examples/cdk/.gitignore b/aws-lambda-java-profiler/examples/cdk/.gitignore new file mode 100644 index 000000000..1db21f162 --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/.gitignore @@ -0,0 +1,13 @@ +.classpath.txt +target +.classpath +.project +.idea +.settings +.vscode +*.iml + +# CDK asset staging directory +.cdk.staging +cdk.out + diff --git a/aws-lambda-java-profiler/examples/cdk/README.md b/aws-lambda-java-profiler/examples/cdk/README.md new file mode 100644 index 000000000..516ef71a2 --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/README.md @@ -0,0 +1,18 @@ +# Welcome to your CDK Java project! + +This is a blank project for CDK development with Java. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +It is a [Maven](https://maven.apache.org/) based project, so you can open this project with any Maven compatible Java IDE to build and run tests. + +## Useful commands + + * `mvn package` compile and run tests + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +Enjoy! diff --git a/aws-lambda-java-profiler/examples/cdk/cdk.json b/aws-lambda-java-profiler/examples/cdk/cdk.json new file mode 100644 index 000000000..e94ff8512 --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/cdk.json @@ -0,0 +1,68 @@ +{ + "app": "mvn -e -q compile exec:java", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "target", + "pom.xml", + "src/test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false + } +} diff --git a/aws-lambda-java-profiler/examples/cdk/pom.xml b/aws-lambda-java-profiler/examples/cdk/pom.xml new file mode 100644 index 000000000..01bbf0d67 --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + com.myorg + example-cdk-profiler-layer + 0.1 + + + UTF-8 + 2.155.0 + [10.0.0,11.0.0) + 5.7.1 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + com.myorg.InfraApp + + + + + + + + software.amazon.awscdk + aws-cdk-lib + ${cdk.version} + + + + software.constructs + constructs + ${constructs.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + diff --git a/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java b/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java new file mode 100644 index 000000000..1232c1b8b --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java @@ -0,0 +1,42 @@ +package com.myorg; + +import software.amazon.awscdk.App; +import software.amazon.awscdk.Environment; +import software.amazon.awscdk.StackProps; + +import java.util.Arrays; + +public class InfraApp { + public static void main(final String[] args) { + App app = new App(); + + new InfraStack(app, "InfraStack", StackProps.builder() + // If you don't specify 'env', this stack will be environment-agnostic. + // Account/Region-dependent features and context lookups will not work, + // but a single synthesized template can be deployed anywhere. + + // Uncomment the next block to specialize this stack for the AWS Account + // and Region that are implied by the current CLI configuration. + /* + .env(Environment.builder() + .account(System.getenv("CDK_DEFAULT_ACCOUNT")) + .region(System.getenv("CDK_DEFAULT_REGION")) + .build()) + */ + + // Uncomment the next block if you know exactly what Account and Region you + // want to deploy the stack to. + /* + .env(Environment.builder() + .account("123456789012") + .region("us-east-1") + .build()) + */ + + // For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html + .build()); + + app.synth(); + } +} + diff --git a/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java b/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java new file mode 100644 index 000000000..181298ac0 --- /dev/null +++ b/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java @@ -0,0 +1,53 @@ +package com.myorg; + +import software.amazon.awscdk.Duration; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.LayerVersion; +import software.amazon.awscdk.services.s3.Bucket; +import software.constructs.Construct; +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.StackProps; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static software.amazon.awscdk.services.lambda.Architecture.*; +import static software.amazon.awscdk.services.lambda.Runtime.*; + +public class InfraStack extends Stack { + public InfraStack(final Construct scope, final String id) { + this(scope, id, null); + } + + public InfraStack(final Construct scope, final String id, final StackProps props) { + super(scope, id, props); + + var resultsBucketName = UUID.randomUUID().toString(); + var resultsBucket = Bucket.Builder.create(this, "profiler-results-bucket") + .bucketName(resultsBucketName) + .build(); + + var layerVersion = LayerVersion.Builder.create(this, "async-profiler-layer") + .compatibleArchitectures(List.of(ARM_64, X86_64)) + .compatibleRuntimes(List.of(JAVA_11, JAVA_17, JAVA_21)) + .code(Code.fromAsset("../../target/extension.zip")) + .build(); + + var environmentVariables = Map.of("JAVA_TOOL_OPTIONS", "-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler.jar", + "PROFILER_RESULTS_BUCKET_NAME", resultsBucketName); + + var function = Function.Builder.create(this, "example-profiler-function") + .runtime(JAVA_21) + .handler("helloworld.App") + .code(Code.fromAsset("../function/profiling-example/target/Helloworld-1.0.jar")) + .memorySize(2048) + .layers(List.of(layerVersion)) + .environment(environmentVariables) + .timeout(Duration.seconds(30)) + .build(); + + resultsBucket.grantPut(function); + } +} diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml b/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml new file mode 100644 index 000000000..c7465bfd8 --- /dev/null +++ b/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + helloworld + HelloWorld + 1.0 + jar + A sample Hello World created for SAM CLI. + + 21 + 21 + + + + + com.amazonaws + aws-lambda-java-core + 1.2.2 + + + com.amazonaws + aws-lambda-java-events + 3.11.0 + + + com.hkupty.penna + penna-core + 0.8.0 + + + org.slf4j + slf4j-api + 2.0.13 + + + + junit + junit + 4.13.2 + test + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + + package + + shade + + + + + + + diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java b/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java new file mode 100644 index 000000000..c58f55a1f --- /dev/null +++ b/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java @@ -0,0 +1,53 @@ +package helloworld; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for requests to Lambda function. + */ +public class App implements RequestHandler { + + private static Logger logger = LoggerFactory.getLogger(App.class); + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + + APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() + .withHeaders(headers); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + logger.info(output); + + return response + .withStatusCode(200) + .withBody(output); + } catch (IOException e) { + return response + .withBody("{}") + .withStatusCode(500); + } + } + + private String getPageContents(String address) throws IOException{ + URL url = new URL(address); + try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } + } +} diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java b/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java new file mode 100644 index 000000000..240323bb7 --- /dev/null +++ b/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java @@ -0,0 +1,22 @@ +package helloworld; + +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public class AppTest { + @Test + public void successfulResponse() { + App app = new App(); + APIGatewayProxyResponseEvent result = app.handleRequest(null, null); + assertEquals(200, result.getStatusCode().intValue()); + assertEquals("application/json", result.getHeaders().get("Content-Type")); + String content = result.getBody(); + assertNotNull(content); + assertTrue(content.contains("\"message\"")); + assertTrue(content.contains("\"hello world\"")); + assertTrue(content.contains("\"location\"")); + } +} diff --git a/aws-lambda-java-profiler/extension/build.gradle b/aws-lambda-java-profiler/extension/build.gradle new file mode 100644 index 000000000..0c8d53e9c --- /dev/null +++ b/aws-lambda-java-profiler/extension/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id "com.gradleup.shadow" version "8.3.3" +} + +repositories { + mavenCentral() +} + +sourceCompatibility = 11 +targetCompatibility = 11 + +dependencies { + implementation 'com.amazonaws:aws-lambda-java-core:1.2.3' + implementation 'com.amazonaws:aws-lambda-java-events:3.11.5' + implementation("tools.profiler:async-profiler:3.0") + implementation("software.amazon.awssdk:s3:2.28.9") { + exclude group: 'software.amazon.awssdk', module: 'netty-nio-client' + } +} + +jar { + manifest { + attributes 'Main-Class': 'com.amazonaws.services.lambda.extension.ExtensionMain' + attributes 'Premain-Class': 'com.amazonaws.services.lambda.extension.PreMain' + attributes 'Can-Redefine-Class': true + } +} + +shadowJar { + archiveFileName = "profiler-extension.jar" +} + +build.dependsOn jar diff --git a/aws-lambda-java-profiler/extension/build_layer.sh b/aws-lambda-java-profiler/extension/build_layer.sh new file mode 100755 index 000000000..cfb381cff --- /dev/null +++ b/aws-lambda-java-profiler/extension/build_layer.sh @@ -0,0 +1,13 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +./gradlew :shadowJar + +chmod +x extensions/profiler-extension +archive="extension.zip" +if [ -f "$archive" ] ; then + rm "$archive" +fi + +zip "$archive" -j build/libs/profiler-extension.jar +zip "$archive" extensions/* \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/extensions/profiler-extension b/aws-lambda-java-profiler/extension/extensions/profiler-extension new file mode 100755 index 000000000..ef9a5e47c --- /dev/null +++ b/aws-lambda-java-profiler/extension/extensions/profiler-extension @@ -0,0 +1,6 @@ +#!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 + +set -euo pipefail +exec -- java -jar /opt/profiler-extension.jar \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar b/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties b/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..df97d72b8 --- /dev/null +++ b/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/aws-lambda-java-profiler/extension/gradlew b/aws-lambda-java-profiler/extension/gradlew new file mode 100755 index 000000000..f5feea6d6 --- /dev/null +++ b/aws-lambda-java-profiler/extension/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/aws-lambda-java-profiler/extension/gradlew.bat b/aws-lambda-java-profiler/extension/gradlew.bat new file mode 100644 index 000000000..9b42019c7 --- /dev/null +++ b/aws-lambda-java-profiler/extension/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java new file mode 100644 index 000000000..8da566311 --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java @@ -0,0 +1,74 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; + +/** + * Utility class that takes care of registration of extension, fetching the next event, initializing + * and exiting with error + */ +public class ExtensionClient { + private static final String EXTENSION_NAME = "profiler-extension"; + private static final String BASEURL = String + .format("http://%s/2020-01-01/extension", System.getenv("AWS_LAMBDA_RUNTIME_API")); + private static final String BODY = "{" + + " \"events\": [" + + " \"INVOKE\"," + + " \"SHUTDOWN\"" + + " ]" + + " }"; + private static final String LAMBDA_EXTENSION_IDENTIFIER = "Lambda-Extension-Identifier"; + private static final String LAMBDA_EXTENSION_FUNCTION_ERROR_TYPE = "Lambda-Extension-Function-Error-Type"; + private static final HttpClient client = HttpClient.newBuilder().build(); + + public static String registerExtension() { + final String registerUrl = String.format("%s/register", BASEURL); + HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(BODY)) + .header("Content-Type", "application/json") + .header("Lambda-Extension-Name", EXTENSION_NAME) + .uri(URI.create(registerUrl)) + .build(); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + // Get extension ID from the response headers + Optional lambdaExtensionHeader = response.headers().firstValue("lambda-extension-identifier"); + if (lambdaExtensionHeader.isPresent()) { + return lambdaExtensionHeader.get(); + } + } + catch (Exception e) { + Logger.error("could not register the extension"); + e.printStackTrace(); + } + throw new RuntimeException("Error while registering extension"); + } + + public static String getNext(final String extensionId) { + try { + final String nextEventUrl = String.format("%s/event/next", BASEURL); + HttpRequest request = HttpRequest.newBuilder() + .GET() + .header(LAMBDA_EXTENSION_IDENTIFIER, extensionId) + .uri(URI.create(nextEventUrl)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + return response.body(); + } else { + Logger.error("invalid status code returned while processing event = " + response.statusCode()); + } + } + catch (Exception e) { + Logger.error("could not get /next event"); + e.printStackTrace(); + } + + return null; + } +} diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java new file mode 100644 index 000000000..a61e378bf --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java @@ -0,0 +1,133 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.URI; +import java.util.UUID; + +public class ExtensionMain { + + private static final HttpClient client = HttpClient.newBuilder().build(); + private static String previousFileSuffix = null; + private static boolean coldstart = true; + private static final String REQUEST_ID = "requestId"; + private static final String EVENT_TYPE = "eventType"; + public static final String HEADER_NAME = "X-FileName"; + + private static S3Manager s3Manager; + + public static void main(String[] args) { + final String extension = ExtensionClient.registerExtension(); + Logger.debug("Extension registration complete, extensionID: " + extension); + s3Manager = new S3Manager(); + while (true) { + try { + String response = ExtensionClient.getNext(extension); + if (response != null && !response.isEmpty()) { + final String eventType = extractInfo(EVENT_TYPE, response); + Logger.debug("eventType = " + eventType); + if (eventType != null) { + switch (eventType) { + case "INVOKE": + handleInvoke(response); + break; + case "SHUTDOWN": + handleShutDown(); + break; + default: + Logger.error("invalid event type received " + eventType); + } + } + } + } catch (Exception e) { + Logger.error("error while processing extension -" + e.getMessage()); + e.printStackTrace(); + } + } + } + + private static void handleShutDown() { + Logger.debug("handling SHUTDOWN event, flushing the last profile"); + try { + // no need to stop the profiler as it has been stopped by the shutdown hook + s3Manager.upload(previousFileSuffix, true); + } catch (Exception e) { + Logger.error("could not start the profiler"); + throw e; + } + System.exit(0); + } + + public static void handleInvoke(String payload) { + final String requestId = extractInfo(REQUEST_ID, payload); + final String randomSuffix = UUID.randomUUID().toString().substring(0,5); + Logger.debug("handling INVOKE event, requestID = " + requestId); + if (!coldstart) { + try { + stopProfiler(previousFileSuffix); + s3Manager.upload(previousFileSuffix, false); + startProfiler(); + } catch (Exception e) { + Logger.error("could not start the profiler"); + throw e; + } + } + coldstart = false; + previousFileSuffix = extractInfo(REQUEST_ID, payload) + "-" + randomSuffix; + } + + private static String extractInfo(String info, String jsonString) { + String prefix = "\"" + info + "\":\""; + String suffix = "\""; + + int startIndex = jsonString.indexOf(prefix); + if (startIndex == -1) { + return null; // requestId not found + } + + startIndex += prefix.length(); + int endIndex = jsonString.indexOf(suffix, startIndex); + + if (endIndex == -1) { + return null; // Malformed JSON + } + + return jsonString.substring(startIndex, endIndex); + } + + private static void startProfiler() { + try { + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:1234/profiler/start")) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + Logger.debug("profiler successfully started"); + } + } catch(Exception e) { + Logger.error("could not start the profiler"); + e.printStackTrace(); + } + } + + private static void stopProfiler(String fileNameSuffix) { + try { + HttpRequest request = HttpRequest.newBuilder() + .GET() + .setHeader(HEADER_NAME, fileNameSuffix) + .uri(URI.create("http://localhost:1234/profiler/stop")) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + Logger.debug("profiler successfully stopped"); + } + } catch(Exception e) { + Logger.error("could not stop the profiler"); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java new file mode 100644 index 000000000..49a4900cc --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +public class Logger { + + private static final boolean IS_DEBUG_ENABLED = initializeDebugFlag(); + private static final String PREFIX = "[PROFILER] "; + + private static boolean initializeDebugFlag() { + String envValue = System.getenv("PROFILER_DEBUG"); + return "true".equalsIgnoreCase(envValue) || "1".equals(envValue); + } + + public static void debug(String message) { + if(IS_DEBUG_ENABLED) { + System.out.println(PREFIX + message); + } + } + + public static void error(String message) { + System.out.println(PREFIX + message); + } + +} \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java new file mode 100644 index 000000000..00d919727 --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java @@ -0,0 +1,110 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.instrument.Instrumentation; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import one.profiler.AsyncProfiler; + +public class PreMain { + + private static final String DEFAULT_PROFILER_START_COMMAND = "start,event=wall,interval=1us"; + private static final String DEFAULT_PROFILER_STOP_COMMAND = "stop,file=%s,include=*AWSLambda.main,include=start_thread"; + private static final String PROFILER_START_COMMAND = System.getenv().getOrDefault("PROFILER_START_COMMAND", DEFAULT_PROFILER_START_COMMAND); + private static final String PROFILER_STOP_COMMAND = System.getenv().getOrDefault("PROFILER_STOP_COMMAND", DEFAULT_PROFILER_STOP_COMMAND); + private static final String INTERNAL_COMMUNICATION_PORT = System.getenv().getOrDefault("PROFILER_COMMUNICATION_PORT", "1234"); + + public static void premain(String agentArgs, Instrumentation inst) { + Logger.debug("premain is starting"); + if(!createFileIfNotExist("/tmp/aws-lambda-java-profiler")) { + Logger.debug("starting the profiler for coldstart"); + startProfiler(); + registerShutdownHook(AsyncProfiler.getInstance()); + try { + Integer port = Integer.parseInt(INTERNAL_COMMUNICATION_PORT); + Logger.debug("using profile communication port = " + port); + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + server.createContext("/profiler/start", new StartProfiler()); + server.createContext("/profiler/stop", new StopProfiler()); + server.setExecutor(null); // Use the default executor + server.start(); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + private static boolean createFileIfNotExist(String filePath) { + File file = new File(filePath); + try { + return file.createNewFile(); + } catch (IOException e) { + System.out.println(e); + return false; + } + } + + public static class StopProfiler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + Logger.debug("hit /profiler/stop"); + final String fileName = exchange.getRequestHeaders().getFirst(ExtensionMain.HEADER_NAME); + stopProfiler(fileName); + String response = "ok"; + exchange.sendResponseHeaders(200, response.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes(StandardCharsets.UTF_8)); + } + } + } + + public static class StartProfiler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + Logger.debug("hit /profiler/start"); + startProfiler(); + String response = "ok"; + exchange.sendResponseHeaders(200, response.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes(StandardCharsets.UTF_8)); + } + } + } + + + public static void stopProfiler(String fileNameSuffix) { + try { + final String fileName = String.format("/tmp/profiling-data-%s.html", fileNameSuffix); + Logger.debug("stopping the profiler with filename = " + fileName + " with command = " + PROFILER_STOP_COMMAND); + AsyncProfiler.getInstance().execute(String.format(PROFILER_STOP_COMMAND, fileName)); + } catch(Exception e) { + Logger.error("could not stop the profiler"); + e.printStackTrace(); + } + } + + public static void startProfiler() { + try { + Logger.debug("staring the profiler with command = " + PROFILER_START_COMMAND); + AsyncProfiler.getInstance().execute(PROFILER_START_COMMAND); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void registerShutdownHook(AsyncProfiler profiler) { + Logger.debug("registering shutdown hook"); + Thread shutdownHook = new Thread(new ShutdownHook(profiler)); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + +} \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java new file mode 100644 index 000000000..2846fbc1f --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java @@ -0,0 +1,67 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +import java.io.File; +import java.time.format.DateTimeFormatter; +import java.time.Instant; +import java.time.LocalDate; + +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; + +public class S3Manager { + + private static final String RESULTS_BUCKET = "PROFILER_RESULTS_BUCKET_NAME"; + private static final String FUNCTION_NAME = System.getenv().getOrDefault("AWS_LAMBDA_FUNCTION_NAME", "function"); + private S3Client s3Client; + private String bucketName; + + public S3Manager() { + final String bucketName = System.getenv(RESULTS_BUCKET); + Logger.debug("creating S3Manager with bucketName = " + bucketName); + if (null == bucketName || bucketName.isEmpty()) { + throw new IllegalArgumentException("please set the bucket name using PROFILER_RESULTS_BUCKET_NAME environment variable"); + } + this.s3Client = S3Client.builder().build(); + this.bucketName = bucketName; + Logger.debug("S3Manager successfully created"); + } + + public void upload(String fileName, boolean isShutDownEvent) { + try { + final String suffix = isShutDownEvent ? "shutdown" : fileName; + final String key = buildKey(FUNCTION_NAME, fileName); + Logger.debug("uploading profile to key = " + key); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build(); + File file = new File(String.format("/tmp/profiling-data-%s.html", suffix)); + if (file.exists()) { + Logger.debug("file size is " + file.length()); + RequestBody requestBody = RequestBody.fromFile(file); + PutObjectResponse response = s3Client.putObject(putObjectRequest, requestBody); + Logger.debug("profile uploaded successfully. ETag: " + response.eTag()); + if(file.delete()) { + Logger.debug("file deleted"); + } + } else { + throw new IllegalArgumentException("could not find the profile to upload"); + } + } catch (Exception e) { + Logger.error("could not upload the profile"); + e.printStackTrace(); + } + } + + private String buildKey(String functionName, String fileName) { + final LocalDate currentDate = LocalDate.now(); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + final String formattedDate = currentDate.format(formatter); + return String.format("%s/%s/%s", formattedDate, functionName, fileName); + } + +} \ No newline at end of file diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java new file mode 100644 index 000000000..d3a0fa5b4 --- /dev/null +++ b/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +package com.amazonaws.services.lambda.extension; + +import one.profiler.AsyncProfiler; + +public class ShutdownHook implements Runnable { + + private AsyncProfiler profiler; + + public ShutdownHook(AsyncProfiler profiler) { + this.profiler = profiler; + } + + @Override + public void run() { + Logger.debug("running ShutdownHook"); + try { + final String fileName = "/tmp/profiling-data-shutdown.html"; + Logger.debug("stopping the profiler"); + AsyncProfiler.getInstance().execute(String.format("stop,file=%s,include=*AWSLambda.main,include=start_thread", fileName)); + } catch (Exception e) { + Logger.error("could not stop the profiler"); + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/cleanup.sh b/aws-lambda-java-profiler/integration_tests/cleanup.sh new file mode 100755 index 000000000..d58142a04 --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/cleanup.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Set variables +LAYER_ARN=$(cat /tmp/layer_arn) +FUNCTION_NAME="aws-lambda-java-profiler-function-${GITHUB_RUN_ID}" +ROLE_NAME="aws-lambda-java-profiler-role-${GITHUB_RUN_ID}" + +# Function to check if a command was successful +check_success() { + if [ $? -eq 0 ]; then + echo "Success: $1" + else + echo "Error: Failed to $1" + exit 1 + fi +} + +# Delete Lambda Layer +echo "Deleting Lambda Layer..." +aws lambda delete-layer-version --layer-name $(echo $LAYER_ARN | cut -d: -f7) --version-number $(echo $LAYER_ARN | cut -d: -f8) +check_success "delete Lambda Layer" + +# Delete Lambda Function +echo "Deleting Lambda Function..." +aws lambda delete-function --function-name $FUNCTION_NAME +check_success "delete Lambda Function" + +# Delete IAM Role +echo "Deleting IAM Role..." +# First, detach all policies from the role +for policy in $(aws iam list-attached-role-policies --role-name $ROLE_NAME --query 'AttachedPolicies[*].PolicyArn' --output text); do + aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn $policy + check_success "detach policy $policy from role $ROLE_NAME" +done + +# Remove s3 inline policy +aws iam delete-role-policy --role-name $ROLE_NAME --policy-name "s3PutObject" +check_success "deleted inline policy" + + +# Then delete the role +aws iam delete-role --role-name $ROLE_NAME +check_success "delete IAM Role" + +echo "All deletions completed successfully." \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/create_bucket.sh b/aws-lambda-java-profiler/integration_tests/create_bucket.sh new file mode 100755 index 000000000..0ba50b732 --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/create_bucket.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +PROFILER_RESULTS_BUCKET_NAME="aws-lambda-java-profiler-bucket-${GITHUB_RUN_ID}" + +# Create the S3 bucket +aws s3 mb s3://"$PROFILER_RESULTS_BUCKET_NAME" + +# Check if the bucket was created successfully +if [ $? -eq 0 ]; then + echo "Bucket '$PROFILER_RESULTS_BUCKET_NAME' created successfully." +else + echo "Error: Failed to create bucket '$PROFILER_RESULTS_BUCKET_NAME'." + exit 1 +fi \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/create_function.sh b/aws-lambda-java-profiler/integration_tests/create_function.sh new file mode 100755 index 000000000..23b07e12c --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/create_function.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Set variables +FUNCTION_NAME="aws-lambda-java-profiler-function-${GITHUB_RUN_ID}" +ROLE_NAME="aws-lambda-java-profiler-role-${GITHUB_RUN_ID}" +HANDLER="helloworld.Handler::handleRequest" +RUNTIME="java21" +LAYER_ARN=$(cat /tmp/layer_arn) + +JAVA_TOOL_OPTIONS="-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar" +PROFILER_RESULTS_BUCKET_NAME="aws-lambda-java-profiler-bucket-${GITHUB_RUN_ID}" + +# Compile the Hello World project +cd integration_tests/helloworld +gradle :buildZip +cd ../.. + +# Create IAM role for Lambda +ROLE_ARN=$(aws iam create-role \ + --role-name $ROLE_NAME \ + --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' \ + --query 'Role.Arn' \ + --output text) + +# Attach basic Lambda execution policy to the role +aws iam attach-role-policy \ + --role-name $ROLE_NAME \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + +# Attach s3:PutObject policy to the role so we can upload profiles +POLICY_DOCUMENT=$(cat < $new_filename" + else + echo "No change: $filename" + fi + fi +done + +echo "All files processed." \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/helloworld/build.gradle b/aws-lambda-java-profiler/integration_tests/helloworld/build.gradle new file mode 100644 index 000000000..927317f8f --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/helloworld/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'java' + +repositories { + mavenCentral() +} + +sourceCompatibility = 21 +targetCompatibility = 21 + +dependencies { + implementation ( + 'com.amazonaws:aws-lambda-java-core:1.2.3', + 'com.amazonaws:aws-lambda-java-events:3.11.0', + 'org.slf4j:slf4j-api:2.0.13' + ) +} + +task buildZip(type: Zip) { + archiveBaseName = "code" + from compileJava + from processResources + into('lib') { + from configurations.runtimeClasspath + } +} + +build.dependsOn buildZip \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/helloworld/src/main/java/helloworld/Handler.java b/aws-lambda-java-profiler/integration_tests/helloworld/src/main/java/helloworld/Handler.java new file mode 100644 index 000000000..a29cae18e --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/helloworld/src/main/java/helloworld/Handler.java @@ -0,0 +1,53 @@ +package helloworld; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.List; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; +import com.amazonaws.services.lambda.runtime.Context; + +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Handler implements RequestHandler { + + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + long start = System.currentTimeMillis(); + List result = slowRecursiveFunction(0, 5); + long end = System.currentTimeMillis(); + long duration = end - start; + + System.out.println("Function execution time: " + duration + " ms"); + System.out.println("Result size: " + result.size()); + System.out.println("First few elements: " + result.subList(0, Math.min(10, result.size()))); + + return new APIGatewayProxyResponseEvent() + .withStatusCode(200) + .withBody("ok"); + + } + + private static List slowRecursiveFunction(int n, int depth) { + List result = new ArrayList<>(); + if (depth == 0) { + return result; + } + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTime < 100) { + // nothing to do here + } + result.add(n); + result.addAll(slowRecursiveFunction(n + 2, depth - 1)); + return result; + } +} diff --git a/aws-lambda-java-profiler/integration_tests/helloworld/src/main/resources/wrapper.sh b/aws-lambda-java-profiler/integration_tests/helloworld/src/main/resources/wrapper.sh new file mode 100755 index 000000000..b54b77673 --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/helloworld/src/main/resources/wrapper.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# the path to the interpreter and all of the originally intended arguments +args=("$@") + +# the extra options to pass to the interpreter +echo "${args[@]}" + +# start the runtime with the extra options +exec "${args[@]}" \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/invoke_function.sh b/aws-lambda-java-profiler/integration_tests/invoke_function.sh new file mode 100755 index 000000000..741eec140 --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/invoke_function.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Set variables +FUNCTION_NAME="aws-lambda-java-profiler-function-${GITHUB_RUN_ID}" +PAYLOAD='{"key": "value"}' + +echo "Invoking Lambda function: $FUNCTION_NAME" + +# Invoke the Lambda function synchronously and capture the response +RESPONSE=$(aws lambda invoke \ + --function-name "$FUNCTION_NAME" \ + --payload "$PAYLOAD" \ + --cli-binary-format raw-in-base64-out \ + --log-type Tail \ + output.json) + +# Extract the status code and log result from the response +STATUS_CODE=$(echo "$RESPONSE" | jq -r '.StatusCode') +LOG_RESULT=$(echo "$RESPONSE" | jq -r '.LogResult') + +echo "Function invocation completed with status code: $STATUS_CODE" + +# Decode and display the logs +if [ -n "$LOG_RESULT" ]; then + echo "Function logs:" + echo "$LOG_RESULT" | base64 --decode +else + echo "No logs available." +fi + +# Display the function output +echo "Function output:" +cat output.json + +echo "$LOG_RESULT" | base64 --decode | grep "starting the profiler for coldstart" || exit 1 +echo "$LOG_RESULT" | base64 --decode | grep -v "uploading" || exit 1 + +# Clean up the output file +rm output.json + + +# Invoke it a second time for warm start +echo "Invoking Lambda function: $FUNCTION_NAME" + +# Invoke the Lambda function synchronously and capture the response +RESPONSE=$(aws lambda invoke \ + --function-name "$FUNCTION_NAME" \ + --payload "$PAYLOAD" \ + --cli-binary-format raw-in-base64-out \ + --log-type Tail \ + output.json) + +# Extract the status code and log result from the response +STATUS_CODE=$(echo "$RESPONSE" | jq -r '.StatusCode') +LOG_RESULT=$(echo "$RESPONSE" | jq -r '.LogResult') + +echo "Function invocation completed with status code: $STATUS_CODE" + +# Decode and display the logs +if [ -n "$LOG_RESULT" ]; then + echo "Function logs:" + echo "$LOG_RESULT" | base64 --decode +else + echo "No logs available." +fi + +# Display the function output +echo "Function output:" +cat output.json + +echo "$LOG_RESULT" | base64 --decode | grep "uploading" || exit 1 + +# Clean up the output file +rm output.json diff --git a/aws-lambda-java-profiler/integration_tests/publish_layer.sh b/aws-lambda-java-profiler/integration_tests/publish_layer.sh new file mode 100755 index 000000000..879944e8e --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/publish_layer.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Set variables +LAYER_NAME="aws-lambda-java-profiler-test" +DESCRIPTION="AWS Lambda Java Profiler Test Layer" +ZIP_FILE="./extension/extension.zip" +RUNTIME="java21" +ARCHITECTURE="x86_64" + +# Check if AWS CLI is installed +if ! command -v aws &> /dev/null; then + echo "AWS CLI is not installed. Please install it first." + exit 1 +fi + +# Check if the ZIP file exists +if [ ! -f "$ZIP_FILE" ]; then + echo "ZIP file $ZIP_FILE not found. Please make sure it exists." + exit 1 +fi + +# Publish the layer +echo "Publishing layer $LAYER_NAME..." +RESPONSE=$(aws lambda publish-layer-version \ + --layer-name "$LAYER_NAME" \ + --description "$DESCRIPTION" \ + --zip-file "fileb://$ZIP_FILE" \ + --compatible-runtimes "$RUNTIME" \ + --compatible-architectures "$ARCHITECTURE") + +# Check if the layer was published successfully +if [ $? -eq 0 ]; then + LAYER_VERSION=$(echo $RESPONSE | jq -r '.Version') + LAYER_ARN=$(echo $RESPONSE | jq -r '.LayerVersionArn') + echo "Layer published successfully!" + echo "Layer Version: $LAYER_VERSION" + echo "Layer ARN: $LAYER_ARN" + echo $LAYER_ARN > /tmp/layer_arn +else + echo "Failed to publish layer. Please check your AWS credentials and permissions." + exit 1 +fi \ No newline at end of file diff --git a/aws-lambda-java-profiler/integration_tests/trigger b/aws-lambda-java-profiler/integration_tests/trigger new file mode 100644 index 000000000..7c4a013e5 --- /dev/null +++ b/aws-lambda-java-profiler/integration_tests/trigger @@ -0,0 +1 @@ +aaa \ No newline at end of file diff --git a/aws-lambda-java-profiler/update-function.sh b/aws-lambda-java-profiler/update-function.sh new file mode 100755 index 000000000..454d188e0 --- /dev/null +++ b/aws-lambda-java-profiler/update-function.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Check if a function name was provided +if [ $# -eq 0 ]; then + echo "Please provide a function name as an argument." + echo "Usage: $0 " + exit 1 +fi + +FUNCTION_NAME="$1" + +# Generate a random lowercase S3 bucket name +RANDOM_SUFFIX=$(uuidgen | tr '[:upper:]' '[:lower:]' | cut -d'-' -f1) +BUCKET_NAME="my-bucket-${RANDOM_SUFFIX}" +echo "Generated bucket name: $BUCKET_NAME" + +# Create the S3 bucket with the random name +aws s3 mb "s3://$BUCKET_NAME" + +# Create a Lambda layer +aws lambda publish-layer-version \ + --layer-name profiler-layer \ + --description "Profiler Layer" \ + --license-info "MIT" \ + --zip-file fileb://extension/extension.zip \ + --compatible-runtimes java11 java17 java21 \ + --compatible-architectures "arm64" "x86_64" + +# Assign the layer to the function +aws lambda update-function-configuration \ + --function-name "$FUNCTION_NAME" \ + --layers $(aws lambda list-layer-versions --layer-name profiler-layer --query 'LayerVersions[0].LayerVersionArn' --output text) + +# Add environment variables +aws lambda update-function-configuration \ + --function-name "$FUNCTION_NAME" \ + --environment "Variables={PROFILER_RESULTS_BUCKET_NAME=$BUCKET_NAME, JAVA_TOOL_OPTIONS=-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar}" + +# Update the function's permissions to write to the S3 bucket +# Get the function's execution role +ROLE_NAME=$(aws lambda get-function --function-name "$FUNCTION_NAME" --query 'Configuration.Role' --output text | awk -F'/' '{print $NF}') + +# Create a policy document +cat << EOF > s3-write-policy.json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::$BUCKET_NAME", + "arn:aws:s3:::$BUCKET_NAME/*" + ] + } + ] +} +EOF + +# Attach the policy to the role +aws iam put-role-policy \ + --role-name "$ROLE_NAME" \ + --policy-name S3WriteAccess \ + --policy-document file://s3-write-policy.json + +echo "Setup completed for function $FUNCTION_NAME with S3 bucket $BUCKET_NAME" +echo "S3 write permissions added to the function's execution role" + +# Clean up temporary files +rm s3-write-policy.json \ No newline at end of file From f805360279213cb558372c01dde628e40f1e5687 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 6 Nov 2024 11:23:37 +0000 Subject: [PATCH 032/173] fix: role name for integration tests (#75) * fix: role name for integration tests * fix: role name for integration tests * fix: role name for integration tests * fix: role name for integration tests --- .github/workflows/aws-lambda-java-profiler.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index dd7a71e01..be2ef17c4 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -1,12 +1,16 @@ name: Run integration tests for aws-lambda-java-profiler on: + pull_request: + branches: [ '*' ] + paths: + - 'aws-lambda-java-profiler/**' + - '.github/workflows/aws-lambda-java-profiler.yml' push: branches: ['*'] paths: - 'aws-lambda-java-profiler/**' - '.github/workflows/aws-lambda-java-profiler.yml' - workflow_dispatch: jobs: @@ -29,8 +33,8 @@ jobs: - name: Issue AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: - aws-region: ${{ secrets.AWS_REGION }} - role-to-assume: ${{ secrets.AWS_ROLE }} + aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }} + role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }} role-session-name: GitHubActionsRunIntegrationTests role-duration-seconds: 900 From 8023cefea7d45fe450dc12f8c4bf3faa9a2e6b4d Mon Sep 17 00:00:00 2001 From: Mark Sailes <45629314+msailes@users.noreply.github.com> Date: Wed, 26 Feb 2025 18:20:41 +0000 Subject: [PATCH 033/173] Update README.md (#77) Added information regarding the alpha status of the release. --- aws-lambda-java-profiler/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-profiler/README.md b/aws-lambda-java-profiler/README.md index 67482a9b7..11c31829d 100644 --- a/aws-lambda-java-profiler/README.md +++ b/aws-lambda-java-profiler/README.md @@ -12,6 +12,8 @@ profiling data and automatically uploads the data as flame graphs to S3. A flame graph of a Java Lambda function

    +This is an alpha release and not yet ready for production use. We're especially interested in early feedback on features, performance, and compatibility. Please send feedback by opening a [GitHub issue](https://github.com/aws/aws-lambda-java-libs/issues/new). + ### Usage To use the profiler you need to @@ -76,12 +78,6 @@ When the agent is constructed, it starts the profiler and registers itself as a A new thread is created to handle calling `/next` and uploading the results of the profiler to S3. The bucket to upload the result to is configurable using an environment variable. -### Project Structure - -- `Agent.java`: Main class that coordinates profiling and S3 uploads. -- `AgentEntry.java`: Entry point for the Java agent. -- `ExtensionClient.java`: Handles communication with the Lambda Extensions API. - ### Troubleshooting - Ensure the Lambda function has the necessary permissions to write to the S3 bucket. From 5274d383c7e342c199e5b0c0e5e484c104f5b7c6 Mon Sep 17 00:00:00 2001 From: Jonathan Tuliani Date: Thu, 6 Mar 2025 16:08:36 +0000 Subject: [PATCH 034/173] Profiler updates (#78) * Profiler updates Moved profiler to 'experimental' folder Added AWS_LAMBDA_ prefix to env vars Updated README.md * fix: IAM permissions, wrapper, port communication and integration test --------- Co-authored-by: Maxime David --- .../workflows/aws-lambda-java-profiler.yml | 14 +-- .gitignore | 6 +- aws-lambda-java-profiler/README.md | 89 -------------- .../aws-lambda-java-profiler}/.gitignore | 0 .../.mvn/wrapper/maven-wrapper.properties | 0 .../aws-lambda-java-profiler/README.md | 110 ++++++++++++++++++ .../docs/Arch_AWS-Lambda_64.svg | 0 .../example-cold-start-flame-graph-small.png | Bin .../docs/example-cold-start-flame-graph.png | Bin .../examples/cdk/.gitignore | 0 .../examples/cdk/README.md | 0 .../examples/cdk/cdk.json | 0 .../examples/cdk/pom.xml | 0 .../cdk/src/main/java/com/myorg/InfraApp.java | 0 .../src/main/java/com/myorg/InfraStack.java | 2 +- .../function/profiling-example/pom.xml | 0 .../src/main/java/helloworld/App.java | 0 .../src/test/java/helloworld/AppTest.java | 0 .../extension/build.gradle | 0 .../extension/build_layer.sh | 0 .../extension/extensions/profiler-extension | 0 .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../extension/gradlew | 0 .../extension/gradlew.bat | 0 .../lambda/extension/ExtensionClient.java | 1 - .../lambda/extension/ExtensionMain.java | 9 +- .../services/lambda/extension/Logger.java | 2 +- .../services/lambda/extension/PreMain.java | 10 +- .../services/lambda/extension/S3Manager.java | 4 +- .../lambda/extension/ShutdownHook.java | 0 .../integration_tests/cleanup.sh | 0 .../integration_tests/create_bucket.sh | 0 .../integration_tests/create_function.sh | 6 +- .../integration_tests/download_from_s3.sh | 0 .../integration_tests/helloworld/build.gradle | 0 .../src/main/java/helloworld/Handler.java | 0 .../helloworld/src/main/resources/wrapper.sh | 0 .../integration_tests/invoke_function.sh | 0 .../integration_tests/publish_layer.sh | 0 .../integration_tests/trigger | 0 .../update-function.sh | 2 +- 42 files changed, 140 insertions(+), 115 deletions(-) delete mode 100644 aws-lambda-java-profiler/README.md rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/.gitignore (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/.mvn/wrapper/maven-wrapper.properties (100%) create mode 100644 experimental/aws-lambda-java-profiler/README.md rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/docs/Arch_AWS-Lambda_64.svg (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/docs/example-cold-start-flame-graph-small.png (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/docs/example-cold-start-flame-graph.png (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/.gitignore (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/README.md (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/cdk.json (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/pom.xml (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/src/main/java/com/myorg/InfraApp.java (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/cdk/src/main/java/com/myorg/InfraStack.java (96%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/function/profiling-example/pom.xml (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/function/profiling-example/src/main/java/helloworld/App.java (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/examples/function/profiling-example/src/test/java/helloworld/AppTest.java (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/build.gradle (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/build_layer.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/extensions/profiler-extension (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/gradle/wrapper/gradle-wrapper.jar (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/gradle/wrapper/gradle-wrapper.properties (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/gradlew (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/gradlew.bat (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java (96%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java (90%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java (90%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java (88%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java (94%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/cleanup.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/create_bucket.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/create_function.sh (87%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/download_from_s3.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/helloworld/build.gradle (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/helloworld/src/main/java/helloworld/Handler.java (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/helloworld/src/main/resources/wrapper.sh (100%) mode change 100755 => 100644 rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/invoke_function.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/publish_layer.sh (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/integration_tests/trigger (100%) rename {aws-lambda-java-profiler => experimental/aws-lambda-java-profiler}/update-function.sh (91%) mode change 100755 => 100644 diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index be2ef17c4..1e0fa879a 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -39,27 +39,27 @@ jobs: role-duration-seconds: 900 - name: Build layer - working-directory: ./aws-lambda-java-profiler/extension + working-directory: ./experimental/aws-lambda-java-profiler/extension run: ./build_layer.sh - name: Publish layer - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/publish_layer.sh - name: Create the bucket layer - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/create_bucket.sh - name: Create Java function - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/create_function.sh - name: Invoke Java function - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/invoke_function.sh - name: Download from s3 - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/download_from_s3.sh - name: Upload profiles @@ -70,5 +70,5 @@ jobs: - name: cleanup if: always() - working-directory: ./aws-lambda-java-profiler + working-directory: ./experimental/aws-lambda-java-profiler run: ./integration_tests/cleanup.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4921077d2..9f99cc415 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ dependency-reduced-pom.xml aws-lambda-java-runtime-interface-client/pom.xml.versionsBackup # profiler -aws-lambda-java-profiler/integration_tests/helloworld/build -aws-lambda-java-profiler/extension/build/ +experimental/aws-lambda-java-profiler/integration_tests/helloworld/build +experimental/aws-lambda-java-profiler/extension/build/ +experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin +!experimental/aws-lambda-java-profiler/extension/gradle/wrapper/*.jar /scratch/ diff --git a/aws-lambda-java-profiler/README.md b/aws-lambda-java-profiler/README.md deleted file mode 100644 index 11c31829d..000000000 --- a/aws-lambda-java-profiler/README.md +++ /dev/null @@ -1,89 +0,0 @@ -

    - AWS Lambda service icon -

    - -

    AWS Lambda Profiler Extension for Java

    - -The Lambda profiler extension allows you to profile your Java functions invoke by invoke, with high fidelity, and no -code changes. It uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce -profiling data and automatically uploads the data as flame graphs to S3. - -

    - A flame graph of a Java Lambda function -

    - -This is an alpha release and not yet ready for production use. We're especially interested in early feedback on features, performance, and compatibility. Please send feedback by opening a [GitHub issue](https://github.com/aws/aws-lambda-java-libs/issues/new). - -### Usage - -To use the profiler you need to - -1. Build the extension in this repo -2. Deploy it as a Lambda Layer -3. Create an S3 bucket for the results, or reuse an existing one -4. Give your function permission to write to the bucket -5. Configure the required environment variables. - -The following [Quick Start](#quick-start) will give you AWS CLI commands to run to get started. There are also [examples](examples) -using infrastructure as code for you to refer to. - -### Quick Start - -- Clone the repo - -```bash -git clone https://github.com/aws/aws-lambda-java-libs -``` - -- Build the extension - -```bash -cd aws-lambda-java-profiler/extension -./build_layer.sh -``` - -- Run the `update-function.sh` script which will create a new S3 bucket, Lambda layer and all the configuration required. - -```bash -cd .. -./update-function.sh YOUR_FUNCTION_NAME -``` - -### Configuration - -#### Required Environment Variables - -| Name | Value | -|------------------------------|-----------------------------------------------------------------------------------------------| -| PROFILER_RESULTS_BUCKET_NAME | Your unique bucket name | -| JAVA_TOOL_OPTIONS | -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar | - -#### Optional Environment Variables - -| Name | Default Value | Options | -|-------------------------------|-----------------------------------------------------------|--------------------------------| -| PROFILER_START_COMMAND | start,event=wall,interval=1us | | -| PROFILER_STOP_COMMAND | stop,file=%s,include=*AWSLambda.main,include=start_thread | file=%s is required | -| PROFILER_DEBUG | false | true - to enable debug logging | -| PROFILER_COMMUNICATION_PORT | 1234 | a valid port number | - -### How does it work? - -In `/src` is the code for a Java agent. It's entry point `AgentEntry.premain()` is executed as the runtime starts up. -The environment variable `JAVA_TOOL_OPTIONS` is used to specify which .jar file the agent is in. The `MANIFEST.MF` file -is used to specify the pre-main class. - -When the agent is constructed, it starts the profiler and registers itself as a Lambda extension for `INVOKE` request. - -A new thread is created to handle calling `/next` and uploading the results of the profiler to S3. The bucket to upload -the result to is configurable using an environment variable. - -### Troubleshooting - -- Ensure the Lambda function has the necessary permissions to write to the S3 bucket. -- Verify that the environment variables are correctly set in your Lambda function configuration. -- Check CloudWatch logs for any error messages from the extension. - -### Contributing - -Contributions to improve the extension are welcome. Please submit pull requests with your proposed changes. diff --git a/aws-lambda-java-profiler/.gitignore b/experimental/aws-lambda-java-profiler/.gitignore similarity index 100% rename from aws-lambda-java-profiler/.gitignore rename to experimental/aws-lambda-java-profiler/.gitignore diff --git a/aws-lambda-java-profiler/.mvn/wrapper/maven-wrapper.properties b/experimental/aws-lambda-java-profiler/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from aws-lambda-java-profiler/.mvn/wrapper/maven-wrapper.properties rename to experimental/aws-lambda-java-profiler/.mvn/wrapper/maven-wrapper.properties diff --git a/experimental/aws-lambda-java-profiler/README.md b/experimental/aws-lambda-java-profiler/README.md new file mode 100644 index 000000000..efef5d0d9 --- /dev/null +++ b/experimental/aws-lambda-java-profiler/README.md @@ -0,0 +1,110 @@ +

    + AWS Lambda service icon +

    + +

    AWS Lambda Profiler Extension for Java

    + +The Lambda profiler extension allows you to profile your Java functions invoke by invoke, with high fidelity, and no +code changes. It uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce +profiling data and automatically uploads the data as HTML flame graphs to S3. + +

    + A flame graph of a Java Lambda function +

    + +## Current status +**This is an alpha release and not yet ready for production use.** We're especially interested in early feedback on usability, features, performance, and compatibility. Please send feedback by opening a [GitHub issue](https://github.com/aws/aws-lambda-java-libs/issues/new). + +The profiler has been tested with Lambda managed runtimes for Java 17 and Java 21. + +## How to use the Lambda Profiler + +To use the profiler you need to + +1. Build the extension in this repo +2. Deploy it as a Lambda Layer and attach the layer to your function +3. Create an S3 bucket for the results, or reuse an existing one +4. Give your function permission to write to the bucket +5. Configure the required environment variables. + +The above assumes you're using the ZIP deployment method with managed runtimes. If you deploy your functions as container images instead, you will need to include the profiler in your Dockerfile at `/opt/extensions/` rather than using a Lambda layer. + +### Quick Start + +The following [Quick Start](#quick-start) gives AWS CLI commands you can run to get started (MacOS/Linux). There are also [examples](examples) using infrastructure as code for you to refer to. + +1. Clone the repo + + ```bash + git clone https://github.com/aws/aws-lambda-java-libs + ``` + +2. Build the extension + + ```bash + cd aws-lambda-java-libs/experimental/aws-lambda-java-profiler/extension + ./build_layer.sh + ``` + +3. Run the `update-function.sh` script which will create a new S3 bucket, Lambda layer and all the configuration required. + + ```bash + cd .. + ./update-function.sh YOUR_FUNCTION_NAME + ``` + +4. Invoke your function and review the flame graph in S3 using your browser. + +### Configuration + +#### Required Environment Variables + +| Name | Value | +|-----------------------------------------|-----------------------------------------------------------------------------------------------| +| AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME | Your unique bucket name | +| JAVA_TOOL_OPTIONS | -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar | + +#### Optional Environment Variables + +| Name | Default Value | Options | +|------------------------------------------|-----------------------------------------------------------|--------------------------------| +| AWS_LAMBDA_PROFILER_START_COMMAND | start,event=wall,interval=1us | | +| AWS_LAMBDA_PROFILER_STOP_COMMAND | stop,file=%s,include=*AWSLambda.main,include=start_thread | file=%s is required | +| AWS_LAMBDA_PROFILER_DEBUG | false | true - to enable debug logging | +| AWS_LAMBDA_PROFILER_COMMUNICATION_PORT | 1234 | a valid port number | + +### How does it work? + +In `/src` is the code for a Java agent. It's entry point `AgentEntry.premain()` is executed as the runtime starts up. +The environment variable `JAVA_TOOL_OPTIONS` is used to specify which `.jar` file the agent is in. The `MANIFEST.MF` file is used to specify the pre-main class. + +When the agent is constructed, it starts the profiler and registers itself as a Lambda extension for `INVOKE` request. + +A new thread is created to handle calling `/next` and uploading the results of the profiler to S3. The bucket to upload +the result to is configurable using an environment variable. + +### Troubleshooting + +- Ensure the Lambda function execution role has the necessary permissions to write to the S3 bucket. +- Verify that the environment variables are set correctly in your Lambda function configuration. +- Check CloudWatch logs for any error messages from the extension. + +## Contributing + +Contributions to improve the Java profiler extension are welcome. Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information on how to report bugs or submit pull requests. + +Issues or contributions to the [async-profiler](https://github.com/async-profiler/async-profiler) itself should be submitted to that project. + +### Security + +If you discover a potential security issue in this project we ask that you notify AWS Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public GitHub issue. + +### Code of conduct + +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). See [CODE_OF_CONDUCT.md](doc/CODE_OF_CONDUCT.md) for more details. + +## License + +This project is licensed under the [Apache 2.0](../../LICENSE) License. It uses the [async-profiler](https://github.com/async-profiler/async-profiler), which is also released under the Apache 2.0 license. + + diff --git a/aws-lambda-java-profiler/docs/Arch_AWS-Lambda_64.svg b/experimental/aws-lambda-java-profiler/docs/Arch_AWS-Lambda_64.svg similarity index 100% rename from aws-lambda-java-profiler/docs/Arch_AWS-Lambda_64.svg rename to experimental/aws-lambda-java-profiler/docs/Arch_AWS-Lambda_64.svg diff --git a/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png b/experimental/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png similarity index 100% rename from aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png rename to experimental/aws-lambda-java-profiler/docs/example-cold-start-flame-graph-small.png diff --git a/aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png b/experimental/aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png similarity index 100% rename from aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png rename to experimental/aws-lambda-java-profiler/docs/example-cold-start-flame-graph.png diff --git a/aws-lambda-java-profiler/examples/cdk/.gitignore b/experimental/aws-lambda-java-profiler/examples/cdk/.gitignore similarity index 100% rename from aws-lambda-java-profiler/examples/cdk/.gitignore rename to experimental/aws-lambda-java-profiler/examples/cdk/.gitignore diff --git a/aws-lambda-java-profiler/examples/cdk/README.md b/experimental/aws-lambda-java-profiler/examples/cdk/README.md similarity index 100% rename from aws-lambda-java-profiler/examples/cdk/README.md rename to experimental/aws-lambda-java-profiler/examples/cdk/README.md diff --git a/aws-lambda-java-profiler/examples/cdk/cdk.json b/experimental/aws-lambda-java-profiler/examples/cdk/cdk.json similarity index 100% rename from aws-lambda-java-profiler/examples/cdk/cdk.json rename to experimental/aws-lambda-java-profiler/examples/cdk/cdk.json diff --git a/aws-lambda-java-profiler/examples/cdk/pom.xml b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml similarity index 100% rename from aws-lambda-java-profiler/examples/cdk/pom.xml rename to experimental/aws-lambda-java-profiler/examples/cdk/pom.xml diff --git a/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java b/experimental/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java similarity index 100% rename from aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java rename to experimental/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraApp.java diff --git a/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java b/experimental/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java similarity index 96% rename from aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java rename to experimental/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java index 181298ac0..79773e39e 100644 --- a/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java +++ b/experimental/aws-lambda-java-profiler/examples/cdk/src/main/java/com/myorg/InfraStack.java @@ -36,7 +36,7 @@ public InfraStack(final Construct scope, final String id, final StackProps props .build(); var environmentVariables = Map.of("JAVA_TOOL_OPTIONS", "-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler.jar", - "PROFILER_RESULTS_BUCKET_NAME", resultsBucketName); + "AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME", resultsBucketName); var function = Function.Builder.create(this, "example-profiler-function") .runtime(JAVA_21) diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml b/experimental/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml similarity index 100% rename from aws-lambda-java-profiler/examples/function/profiling-example/pom.xml rename to experimental/aws-lambda-java-profiler/examples/function/profiling-example/pom.xml diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java b/experimental/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java similarity index 100% rename from aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java rename to experimental/aws-lambda-java-profiler/examples/function/profiling-example/src/main/java/helloworld/App.java diff --git a/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java b/experimental/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java similarity index 100% rename from aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java rename to experimental/aws-lambda-java-profiler/examples/function/profiling-example/src/test/java/helloworld/AppTest.java diff --git a/aws-lambda-java-profiler/extension/build.gradle b/experimental/aws-lambda-java-profiler/extension/build.gradle similarity index 100% rename from aws-lambda-java-profiler/extension/build.gradle rename to experimental/aws-lambda-java-profiler/extension/build.gradle diff --git a/aws-lambda-java-profiler/extension/build_layer.sh b/experimental/aws-lambda-java-profiler/extension/build_layer.sh similarity index 100% rename from aws-lambda-java-profiler/extension/build_layer.sh rename to experimental/aws-lambda-java-profiler/extension/build_layer.sh diff --git a/aws-lambda-java-profiler/extension/extensions/profiler-extension b/experimental/aws-lambda-java-profiler/extension/extensions/profiler-extension similarity index 100% rename from aws-lambda-java-profiler/extension/extensions/profiler-extension rename to experimental/aws-lambda-java-profiler/extension/extensions/profiler-extension diff --git a/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar b/experimental/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar rename to experimental/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.jar diff --git a/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties b/experimental/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties rename to experimental/aws-lambda-java-profiler/extension/gradle/wrapper/gradle-wrapper.properties diff --git a/aws-lambda-java-profiler/extension/gradlew b/experimental/aws-lambda-java-profiler/extension/gradlew similarity index 100% rename from aws-lambda-java-profiler/extension/gradlew rename to experimental/aws-lambda-java-profiler/extension/gradlew diff --git a/aws-lambda-java-profiler/extension/gradlew.bat b/experimental/aws-lambda-java-profiler/extension/gradlew.bat similarity index 100% rename from aws-lambda-java-profiler/extension/gradlew.bat rename to experimental/aws-lambda-java-profiler/extension/gradlew.bat diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java similarity index 96% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java index 8da566311..60c13a811 100644 --- a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionClient.java @@ -23,7 +23,6 @@ public class ExtensionClient { " ]" + " }"; private static final String LAMBDA_EXTENSION_IDENTIFIER = "Lambda-Extension-Identifier"; - private static final String LAMBDA_EXTENSION_FUNCTION_ERROR_TYPE = "Lambda-Extension-Function-Error-Type"; private static final HttpClient client = HttpClient.newBuilder().build(); public static String registerExtension() { diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java similarity index 90% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java index a61e378bf..480025ade 100644 --- a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java @@ -15,6 +15,7 @@ public class ExtensionMain { private static boolean coldstart = true; private static final String REQUEST_ID = "requestId"; private static final String EVENT_TYPE = "eventType"; + private static final String INTERNAL_COMMUNICATION_PORT = System.getenv().getOrDefault("AWS_LAMBDA_PROFILER_COMMUNICATION_PORT", "1234"); public static final String HEADER_NAME = "X-FileName"; private static S3Manager s3Manager; @@ -100,9 +101,10 @@ private static String extractInfo(String info, String jsonString) { private static void startProfiler() { try { - HttpRequest request = HttpRequest.newBuilder() + String url = String.format("http://localhost:%s/profiler/start", INTERNAL_COMMUNICATION_PORT); + HttpRequest request = HttpRequest.newBuilder() .GET() - .uri(URI.create("http://localhost:1234/profiler/start")) + .uri(URI.create(url)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { @@ -116,10 +118,11 @@ private static void startProfiler() { private static void stopProfiler(String fileNameSuffix) { try { + String url = String.format("http://localhost:%s/profiler/stop", INTERNAL_COMMUNICATION_PORT); HttpRequest request = HttpRequest.newBuilder() .GET() .setHeader(HEADER_NAME, fileNameSuffix) - .uri(URI.create("http://localhost:1234/profiler/stop")) + .uri(URI.create(url)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 200) { diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java similarity index 90% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java index 49a4900cc..e064da101 100644 --- a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/Logger.java @@ -8,7 +8,7 @@ public class Logger { private static final String PREFIX = "[PROFILER] "; private static boolean initializeDebugFlag() { - String envValue = System.getenv("PROFILER_DEBUG"); + String envValue = System.getenv("AWS_LAMBDA_PROFILER_DEBUG"); return "true".equalsIgnoreCase(envValue) || "1".equals(envValue); } diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java similarity index 88% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java index 00d919727..4e968eb75 100644 --- a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java @@ -17,11 +17,11 @@ public class PreMain { - private static final String DEFAULT_PROFILER_START_COMMAND = "start,event=wall,interval=1us"; - private static final String DEFAULT_PROFILER_STOP_COMMAND = "stop,file=%s,include=*AWSLambda.main,include=start_thread"; - private static final String PROFILER_START_COMMAND = System.getenv().getOrDefault("PROFILER_START_COMMAND", DEFAULT_PROFILER_START_COMMAND); - private static final String PROFILER_STOP_COMMAND = System.getenv().getOrDefault("PROFILER_STOP_COMMAND", DEFAULT_PROFILER_STOP_COMMAND); - private static final String INTERNAL_COMMUNICATION_PORT = System.getenv().getOrDefault("PROFILER_COMMUNICATION_PORT", "1234"); + private static final String DEFAULT_AWS_LAMBDA_PROFILER_START_COMMAND = "start,event=wall,interval=1us"; + private static final String DEFAULT_AWS_LAMBDA_PROFILER_STOP_COMMAND = "stop,file=%s,include=*AWSLambda.main,include=start_thread"; + private static final String PROFILER_START_COMMAND = System.getenv().getOrDefault("AWS_LAMBDA_PROFILER_START_COMMAND", DEFAULT_AWS_LAMBDA_PROFILER_START_COMMAND); + private static final String PROFILER_STOP_COMMAND = System.getenv().getOrDefault("AWS_LAMBDA_PROFILER_STOP_COMMAND", DEFAULT_AWS_LAMBDA_PROFILER_STOP_COMMAND); + private static final String INTERNAL_COMMUNICATION_PORT = System.getenv().getOrDefault("AWS_LAMBDA_PROFILER_COMMUNICATION_PORT", "1234"); public static void premain(String agentArgs, Instrumentation inst) { Logger.debug("premain is starting"); diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java similarity index 94% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java index 2846fbc1f..3b55984c5 100644 --- a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/S3Manager.java @@ -14,7 +14,7 @@ public class S3Manager { - private static final String RESULTS_BUCKET = "PROFILER_RESULTS_BUCKET_NAME"; + private static final String RESULTS_BUCKET = "AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME"; private static final String FUNCTION_NAME = System.getenv().getOrDefault("AWS_LAMBDA_FUNCTION_NAME", "function"); private S3Client s3Client; private String bucketName; @@ -23,7 +23,7 @@ public S3Manager() { final String bucketName = System.getenv(RESULTS_BUCKET); Logger.debug("creating S3Manager with bucketName = " + bucketName); if (null == bucketName || bucketName.isEmpty()) { - throw new IllegalArgumentException("please set the bucket name using PROFILER_RESULTS_BUCKET_NAME environment variable"); + throw new IllegalArgumentException("please set the bucket name using AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME environment variable"); } this.s3Client = S3Client.builder().build(); this.bucketName = bucketName; diff --git a/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java similarity index 100% rename from aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java rename to experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java diff --git a/aws-lambda-java-profiler/integration_tests/cleanup.sh b/experimental/aws-lambda-java-profiler/integration_tests/cleanup.sh similarity index 100% rename from aws-lambda-java-profiler/integration_tests/cleanup.sh rename to experimental/aws-lambda-java-profiler/integration_tests/cleanup.sh diff --git a/aws-lambda-java-profiler/integration_tests/create_bucket.sh b/experimental/aws-lambda-java-profiler/integration_tests/create_bucket.sh similarity index 100% rename from aws-lambda-java-profiler/integration_tests/create_bucket.sh rename to experimental/aws-lambda-java-profiler/integration_tests/create_bucket.sh diff --git a/aws-lambda-java-profiler/integration_tests/create_function.sh b/experimental/aws-lambda-java-profiler/integration_tests/create_function.sh similarity index 87% rename from aws-lambda-java-profiler/integration_tests/create_function.sh rename to experimental/aws-lambda-java-profiler/integration_tests/create_function.sh index 23b07e12c..114909d09 100755 --- a/aws-lambda-java-profiler/integration_tests/create_function.sh +++ b/experimental/aws-lambda-java-profiler/integration_tests/create_function.sh @@ -8,7 +8,7 @@ RUNTIME="java21" LAYER_ARN=$(cat /tmp/layer_arn) JAVA_TOOL_OPTIONS="-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar" -PROFILER_RESULTS_BUCKET_NAME="aws-lambda-java-profiler-bucket-${GITHUB_RUN_ID}" +AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME="aws-lambda-java-profiler-bucket-${GITHUB_RUN_ID}" # Compile the Hello World project cd integration_tests/helloworld @@ -35,7 +35,7 @@ POLICY_DOCUMENT=$(cat < Date: Fri, 7 Mar 2025 13:12:04 +0000 Subject: [PATCH 035/173] Update profiler README.md (#79) * Update profiler README.md Update license details in profiler README.md * Fix profiler links on main README Add /experimental/ path for profiler links on main README * README tweaks --- README.md | 4 ++-- experimental/aws-lambda-java-profiler/README.md | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f16e853cc..fdc08a759 100644 --- a/README.md +++ b/README.md @@ -142,14 +142,14 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio ## Lambda Profiler Extension for Java - aws-lambda-java-profiler

    - A flame graph of a Java Lambda function + A flame graph of a Java Lambda function

    This project allows you to profile your Java functions invoke by invoke, with high fidelity, and no code changes. It uses the [async-profiler](https://github.com/async-profiler/async-profiler) project to produce profiling data and automatically uploads the data as flame graphs to S3. -Follow our [Quick Start](aws-lambda-java-profiler) to profile your functions. +Follow our [Quick Start](experimental/aws-lambda-java-profiler#quick-start) to profile your functions. ## Java implementation of the Runtime Interface Client API - aws-lambda-java-runtime-interface-client [![Maven](https://img.shields.io/maven-central/v/com.amazonaws/aws-lambda-java-runtime-interface-client.svg?label=Maven)](https://central.sonatype.com/artifact/com.amazonaws/aws-lambda-java-runtime-interface-client) diff --git a/experimental/aws-lambda-java-profiler/README.md b/experimental/aws-lambda-java-profiler/README.md index efef5d0d9..aec73f1a6 100644 --- a/experimental/aws-lambda-java-profiler/README.md +++ b/experimental/aws-lambda-java-profiler/README.md @@ -105,6 +105,9 @@ This project has adopted the [Amazon Open Source Code of Conduct](https://aws.gi ## License -This project is licensed under the [Apache 2.0](../../LICENSE) License. It uses the [async-profiler](https://github.com/async-profiler/async-profiler), which is also released under the Apache 2.0 license. +This project is licensed under the [Apache 2.0](../../LICENSE) License. It uses the following projects: +- [async-profiler](https://github.com/async-profiler/async-profiler) (Apache 2.0 license) +- [AWS SDK for Java 2.0](https://github.com/aws/aws-sdk-java-v2) (Apache 2.0 license) +- Other libraries in this repository (Apache 2.0 license) From 4a954d5ed243619c23b5b51d4f5fc246e9d35909 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 18 Mar 2025 11:06:35 +0000 Subject: [PATCH 036/173] fix: github action workflow name --- .github/workflows/aws-lambda-java-profiler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 1e0fa879a..7eeb75b25 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -14,7 +14,7 @@ on: jobs: - publish: + build: runs-on: ubuntu-latest permissions: From 8a89013618b2f7895fb63cdd8591752b85942eb4 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 18 Mar 2025 11:36:44 +0000 Subject: [PATCH 037/173] feat: wait for completion --- experimental/aws-lambda-java-profiler/update-function.sh | 4 ++++ 1 file changed, 4 insertions(+) mode change 100644 => 100755 experimental/aws-lambda-java-profiler/update-function.sh diff --git a/experimental/aws-lambda-java-profiler/update-function.sh b/experimental/aws-lambda-java-profiler/update-function.sh old mode 100644 new mode 100755 index 5c9e0c8dd..2606a67e6 --- a/experimental/aws-lambda-java-profiler/update-function.sh +++ b/experimental/aws-lambda-java-profiler/update-function.sh @@ -31,6 +31,10 @@ aws lambda update-function-configuration \ --function-name "$FUNCTION_NAME" \ --layers $(aws lambda list-layer-versions --layer-name profiler-layer --query 'LayerVersions[0].LayerVersionArn' --output text) +# Wait for the function is updated +aws lambda wait function-updated \ + --function-name "$FUNCTION_NAME" + # Add environment variables aws lambda update-function-configuration \ --function-name "$FUNCTION_NAME" \ From ca3950433461f6f1c894b08991a8bb26a1d94544 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 18 Mar 2025 11:38:28 +0000 Subject: [PATCH 038/173] fix: remove useless file --- experimental/aws-lambda-java-profiler/integration_tests/trigger | 1 - 1 file changed, 1 deletion(-) delete mode 100644 experimental/aws-lambda-java-profiler/integration_tests/trigger diff --git a/experimental/aws-lambda-java-profiler/integration_tests/trigger b/experimental/aws-lambda-java-profiler/integration_tests/trigger deleted file mode 100644 index 7c4a013e5..000000000 --- a/experimental/aws-lambda-java-profiler/integration_tests/trigger +++ /dev/null @@ -1 +0,0 @@ -aaa \ No newline at end of file From 574ac85535415242c4ea0817290f5b11e6ab3a8e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 18 Mar 2025 11:40:12 +0000 Subject: [PATCH 039/173] feat: wait for completion --- experimental/aws-lambda-java-profiler/update-function.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/aws-lambda-java-profiler/update-function.sh b/experimental/aws-lambda-java-profiler/update-function.sh index 2606a67e6..b5fda6ee9 100755 --- a/experimental/aws-lambda-java-profiler/update-function.sh +++ b/experimental/aws-lambda-java-profiler/update-function.sh @@ -31,7 +31,7 @@ aws lambda update-function-configuration \ --function-name "$FUNCTION_NAME" \ --layers $(aws lambda list-layer-versions --layer-name profiler-layer --query 'LayerVersions[0].LayerVersionArn' --output text) -# Wait for the function is updated +# Wait for the function to be updated aws lambda wait function-updated \ --function-name "$FUNCTION_NAME" From e1f58736dbced0d0d953fe1d998316eb381938af Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:00:15 +0000 Subject: [PATCH 040/173] Refactor the Main Function Into Three Main Blocks. (#526) * Refactor the Main Function Into Three Main Blocks. * Use Try with Resources for logger. * Fix Linting Errors --------- Co-authored-by: Mohammed Ehab --- .../lambda/runtime/api/client/AWSLambda.java | 97 ++++++++++--------- .../client/logging/LambdaContextLogger.java | 10 +- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 986f8b7b3..2eeb14e3d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -2,6 +2,7 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ + package com.amazonaws.services.lambda.runtime.api.client; import com.amazonaws.services.lambda.crac.Core; @@ -35,7 +36,6 @@ import java.security.Security; import java.util.Properties; - /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: * @@ -137,6 +137,42 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin return requestHandler; } + private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException { + UnsafeUtil.disableIllegalAccessWarning(); + + System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8")); + System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8")); + setupRuntimeLogger(lambdaLogger); + + runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); + + String taskRoot = System.getProperty("user.dir"); + String libRoot = "/opt/java"; + // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes + // are loaded from the system classloader. + customerClassLoader = new CustomerClassLoader(taskRoot, libRoot, ClassLoader.getSystemClassLoader()); + Thread.currentThread().setContextClassLoader(customerClassLoader); + + // Load the user's handler + LambdaRequestHandler requestHandler = null; + try { + requestHandler = findRequestHandler(handler, customerClassLoader); + } catch (UserFault userFault) { + lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + LambdaError error = new LambdaError( + LambdaErrorConverter.fromUserFault(userFault), + RapidErrorType.BadFunctionCode); + runtimeClient.reportInitError(error); + System.exit(1); + } + + if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { + onInitComplete(lambdaLogger); + } + + return requestHandler; + } + public static void setupRuntimeLogger(LambdaLogger lambdaLogger) throws ClassNotFoundException { ReflectUtil.setStaticField( @@ -176,55 +212,27 @@ private static LogSink createLogSink() { } } - public static void main(String[] args) { - startRuntime(args[0]); - } + public static void main(String[] args) throws Throwable { + try (LambdaContextLogger logger = initLogger()) { + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger); + startRuntimeLoop(lambdaRequestHandler, logger); - private static void startRuntime(String handler) { - try (LogSink logSink = createLogSink()) { - LambdaContextLogger logger = new LambdaContextLogger( - logSink, - LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL), - LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT) - ); - startRuntime(handler, logger); - } catch (Throwable t) { + } catch (IOException | ClassNotFoundException t) { throw new Error(t); } } - private static void startRuntime(String handler, LambdaContextLogger lambdaLogger) throws Throwable { - UnsafeUtil.disableIllegalAccessWarning(); - - System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8")); - System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8")); - setupRuntimeLogger(lambdaLogger); + private static LambdaContextLogger initLogger() { + LogSink logSink = createLogSink(); + LambdaContextLogger logger = new LambdaContextLogger( + logSink, + LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL), + LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT)); - runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); - - String taskRoot = System.getProperty("user.dir"); - String libRoot = "/opt/java"; - // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes - // are loaded from the system classloader. - customerClassLoader = new CustomerClassLoader(taskRoot, libRoot, ClassLoader.getSystemClassLoader()); - Thread.currentThread().setContextClassLoader(customerClassLoader); + return logger; + } - // Load the user's handler - LambdaRequestHandler requestHandler; - try { - requestHandler = findRequestHandler(handler, customerClassLoader); - } catch (UserFault userFault) { - lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); - LambdaError error = new LambdaError( - LambdaErrorConverter.fromUserFault(userFault), - RapidErrorType.BadFunctionCode); - runtimeClient.reportInitError(error); - System.exit(1); - return; - } - if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { - onInitComplete(lambdaLogger); - } + private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable { boolean shouldExit = false; while (!shouldExit) { UserFault userFault = null; @@ -240,7 +248,7 @@ private static void startRuntime(String handler, LambdaContextLogger lambdaLogge payload = requestHandler.call(request); runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); // clear interrupted flag in case if it was set by user's code - boolean ignored = Thread.interrupted(); + Thread.interrupted(); } catch (UserFault f) { shouldExit = f.fatal; userFault = f; @@ -278,6 +286,7 @@ static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOExce RapidErrorType.BeforeCheckpointError)); System.exit(64); } + try { Core.getGlobalContext().afterRestore(null); } catch (Exception restoreExc) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java index 693eb015a..dd3569126 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/LambdaContextLogger.java @@ -7,9 +7,11 @@ import com.amazonaws.services.lambda.runtime.logging.LogFormat; import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import java.io.Closeable; +import java.io.IOException; import static java.nio.charset.StandardCharsets.UTF_8; -public class LambdaContextLogger extends AbstractLambdaLogger { +public class LambdaContextLogger extends AbstractLambdaLogger implements Closeable { // If a null string is passed in, replace it with "null", // replicating the behavior of System.out.println(null); private static final byte[] NULL_BYTES_VALUE = "null".getBytes(UTF_8); @@ -29,4 +31,10 @@ protected void logMessage(byte[] message, LogLevel logLevel) { sink.log(logLevel, this.logFormat, message); } } + + @Override + public void close() throws IOException { + sink.close(); + + } } From 7264566579e7094a97f9b23ec580afd7f580de2c Mon Sep 17 00:00:00 2001 From: Talal Al-Humaidi <77169738+Talhumaidi@users.noreply.github.com> Date: Wed, 19 Mar 2025 09:28:28 +0000 Subject: [PATCH 041/173] Fix env vars override bug. (#531) Currently, the script overrides existing env vars. This is dangerous, especially if someone used it by mistake for production lambda. --- .../update-function.sh | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/experimental/aws-lambda-java-profiler/update-function.sh b/experimental/aws-lambda-java-profiler/update-function.sh index b5fda6ee9..e849246a6 100755 --- a/experimental/aws-lambda-java-profiler/update-function.sh +++ b/experimental/aws-lambda-java-profiler/update-function.sh @@ -35,10 +35,27 @@ aws lambda update-function-configuration \ aws lambda wait function-updated \ --function-name "$FUNCTION_NAME" -# Add environment variables +# Get existing environment variables (handle null case) +EXISTING_VARS=$(aws lambda get-function-configuration --function-name "$FUNCTION_NAME" --query "Environment.Variables" --output json 2>/dev/null) +if [[ -z "$EXISTING_VARS" || "$EXISTING_VARS" == "null" ]]; then + EXISTING_VARS="{}" +fi + +# Define new environment variables in JSON format +NEW_VARS=$(jq -n --arg bucket "$BUCKET_NAME" \ + --arg java_opts "-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar" \ + '{AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME: $bucket, JAVA_TOOL_OPTIONS: $java_opts}') + +# Merge existing and new variables (compact JSON output) +UPDATED_VARS=$(echo "$EXISTING_VARS" | jq -c --argjson new_vars "$NEW_VARS" '. + $new_vars') + +# Convert JSON to "Key=Value" format for AWS CLI +ENV_VARS_FORMATTED=$(echo "$UPDATED_VARS" | jq -r 'to_entries | map("\(.key)=\(.value)") | join(",")') + +# Update Lambda function with correct format aws lambda update-function-configuration \ - --function-name "$FUNCTION_NAME" \ - --environment "Variables={AWS_LAMBDA_PROFILER_RESULTS_BUCKET_NAME=$BUCKET_NAME, JAVA_TOOL_OPTIONS=-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -javaagent:/opt/profiler-extension.jar}" + --function-name "$FUNCTION_NAME" \ + --environment "Variables={$ENV_VARS_FORMATTED}" # Update the function's permissions to write to the S3 bucket # Get the function's execution role @@ -73,4 +90,4 @@ echo "Setup completed for function $FUNCTION_NAME with S3 bucket $BUCKET_NAME" echo "S3 write permissions added to the function's execution role" # Clean up temporary files -rm s3-write-policy.json \ No newline at end of file +rm s3-write-policy.json From e66ddcf5b7749f9402e73fc6e207d9b1d7ecaad1 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 19 Mar 2025 10:07:15 +0000 Subject: [PATCH 042/173] fix: Readme and troubleshooting section (#530) * fix: readme * fix: path for GitHub Action --- .github/workflows/aws-lambda-java-profiler.yml | 4 ++-- experimental/aws-lambda-java-profiler/README.md | 1 + experimental/aws-lambda-java-profiler/extension/build.gradle | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 7eeb75b25..db9fc225e 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -4,12 +4,12 @@ on: pull_request: branches: [ '*' ] paths: - - 'aws-lambda-java-profiler/**' + - 'experimental/aws-lambda-java-profiler/**' - '.github/workflows/aws-lambda-java-profiler.yml' push: branches: ['*'] paths: - - 'aws-lambda-java-profiler/**' + - 'experimental/aws-lambda-java-profiler/**' - '.github/workflows/aws-lambda-java-profiler.yml' jobs: diff --git a/experimental/aws-lambda-java-profiler/README.md b/experimental/aws-lambda-java-profiler/README.md index aec73f1a6..ccc66399e 100644 --- a/experimental/aws-lambda-java-profiler/README.md +++ b/experimental/aws-lambda-java-profiler/README.md @@ -88,6 +88,7 @@ the result to is configurable using an environment variable. - Ensure the Lambda function execution role has the necessary permissions to write to the S3 bucket. - Verify that the environment variables are set correctly in your Lambda function configuration. - Check CloudWatch logs for any error messages from the extension. +- The profiler extension uses dependencies such as `com.amazonaws:aws-lambda-java-core`, `com.amazonaws:aws-lambda-java-events` and `software.amazon.awssdk:s3`. If you're using the same dependencies in your Lambda function, make sure that the versions match those used by the extension as mismatched versions can lead to compatibility issues. ## Contributing diff --git a/experimental/aws-lambda-java-profiler/extension/build.gradle b/experimental/aws-lambda-java-profiler/extension/build.gradle index 0c8d53e9c..387bb3528 100644 --- a/experimental/aws-lambda-java-profiler/extension/build.gradle +++ b/experimental/aws-lambda-java-profiler/extension/build.gradle @@ -14,7 +14,7 @@ dependencies { implementation 'com.amazonaws:aws-lambda-java-core:1.2.3' implementation 'com.amazonaws:aws-lambda-java-events:3.11.5' implementation("tools.profiler:async-profiler:3.0") - implementation("software.amazon.awssdk:s3:2.28.9") { + implementation("software.amazon.awssdk:s3:2.31.2") { exclude group: 'software.amazon.awssdk', module: 'netty-nio-client' } } From dfb89116693b63750dbecec3c17809f029f856ae Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 24 Mar 2025 14:37:50 +0000 Subject: [PATCH 043/173] fix: samples (#535) * fix: samples * fix: samples --- .github/workflows/samples.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index c1131648b..8346b7c2f 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/samples.yml' jobs: - build-kinesis-sample: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -28,6 +28,9 @@ jobs: # Install events module - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml + # Install serialization module + - name: Install serialization with Maven + run: mvn -B install --file aws-lambda-java-serialization/pom.xml # Install tests module - name: Install tests with Maven run: mvn -B install --file aws-lambda-java-tests/pom.xml From e8d88b00270f94838a9876f290d0af70b46b73e6 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 31 Mar 2025 15:01:36 +0100 Subject: [PATCH 044/173] fix: use PROFILER_STOP_COMMAND in Shutdown hooks (#537) --- .../com/amazonaws/services/lambda/extension/PreMain.java | 6 +++--- .../amazonaws/services/lambda/extension/ShutdownHook.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java index 4e968eb75..c0522641a 100644 --- a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/PreMain.java @@ -28,7 +28,7 @@ public static void premain(String agentArgs, Instrumentation inst) { if(!createFileIfNotExist("/tmp/aws-lambda-java-profiler")) { Logger.debug("starting the profiler for coldstart"); startProfiler(); - registerShutdownHook(AsyncProfiler.getInstance()); + registerShutdownHook(); try { Integer port = Integer.parseInt(INTERNAL_COMMUNICATION_PORT); Logger.debug("using profile communication port = " + port); @@ -101,9 +101,9 @@ public static void startProfiler() { } } - public static void registerShutdownHook(AsyncProfiler profiler) { + public static void registerShutdownHook() { Logger.debug("registering shutdown hook"); - Thread shutdownHook = new Thread(new ShutdownHook(profiler)); + Thread shutdownHook = new Thread(new ShutdownHook(PROFILER_STOP_COMMAND)); Runtime.getRuntime().addShutdownHook(shutdownHook); } diff --git a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java index d3a0fa5b4..a36584bc1 100644 --- a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ShutdownHook.java @@ -6,10 +6,10 @@ public class ShutdownHook implements Runnable { - private AsyncProfiler profiler; + private String stopCommand; - public ShutdownHook(AsyncProfiler profiler) { - this.profiler = profiler; + public ShutdownHook(String stopCommand) { + this.stopCommand = stopCommand; } @Override @@ -18,7 +18,7 @@ public void run() { try { final String fileName = "/tmp/profiling-data-shutdown.html"; Logger.debug("stopping the profiler"); - AsyncProfiler.getInstance().execute(String.format("stop,file=%s,include=*AWSLambda.main,include=start_thread", fileName)); + AsyncProfiler.getInstance().execute(String.format(this.stopCommand, fileName)); } catch (Exception e) { Logger.error("could not stop the profiler"); } From 167b94d467fa83c0f7ddf64e619ac0c5e0223a27 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 10 Apr 2025 18:29:47 +0100 Subject: [PATCH 045/173] feat: add changelog for profiler (#538) --- experimental/aws-lambda-java-profiler/RELEASE.CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 experimental/aws-lambda-java-profiler/RELEASE.CHANGELOG.md diff --git a/experimental/aws-lambda-java-profiler/RELEASE.CHANGELOG.md b/experimental/aws-lambda-java-profiler/RELEASE.CHANGELOG.md new file mode 100644 index 000000000..f2f14ae48 --- /dev/null +++ b/experimental/aws-lambda-java-profiler/RELEASE.CHANGELOG.md @@ -0,0 +1,7 @@ +### March 31, 2025 +`0.1.1` [link to tag](https://github.com/aws/aws-lambda-java-libs/releases/tag/profiler-extension-0.1.1) +- fix: use PROFILER_STOP_COMMAND in Shutdown hooks ([#537](https://github.com/aws/aws-lambda-java-libs/pull/537)) + +### March 18, 2025 +`0.1.0` [link to tag](https://github.com/aws/aws-lambda-java-libs/releases/tag/profiler-extension-0.1.0) +- Initial release \ No newline at end of file From c5a4d2ea14a16868860fc45079d338032c84d863 Mon Sep 17 00:00:00 2001 From: Steffen Grunwald Date: Thu, 10 Apr 2025 19:37:49 +0200 Subject: [PATCH 046/173] fixed file upload error message (#539) --- .../amazonaws/services/lambda/extension/ExtensionMain.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java index 480025ade..18115a9fd 100644 --- a/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java +++ b/experimental/aws-lambda-java-profiler/extension/src/main/java/com/amazonaws/services/lambda/extension/ExtensionMain.java @@ -56,7 +56,7 @@ private static void handleShutDown() { // no need to stop the profiler as it has been stopped by the shutdown hook s3Manager.upload(previousFileSuffix, true); } catch (Exception e) { - Logger.error("could not start the profiler"); + Logger.error("could not upload the file"); throw e; } System.exit(0); @@ -133,4 +133,4 @@ private static void stopProfiler(String fileNameSuffix) { e.printStackTrace(); } } -} \ No newline at end of file +} From 150b1b73bfad950cce9948ff836d80d80aa3dc1f Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:53:08 +0100 Subject: [PATCH 047/173] feat: add tenant id to lambda context and structured log message (#540) Co-authored-by: Ramisa Alam --- .../api/client/EventHandlerLoader.java | 1 + .../runtime/api/client/api/LambdaContext.java | 7 ++ .../api/client/logging/JsonLogFormatter.java | 1 + .../client/logging/StructuredLogMessage.java | 1 + .../runtimeapi/dto/InvocationRequest.java | 13 ++++ ...ime_api_client_runtimeapi_NativeClient.cpp | 6 ++ .../include/aws/lambda-runtime/runtime.h | 5 ++ .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 5 ++ .../api/client/api/LambdaContextTest.java | 3 +- .../client/logging/JsonLogFormatterTest.java | 20 +++++ .../LambdaRuntimeApiClientImplTest.java | 78 ++++++++++++++++--- 11 files changed, 127 insertions(+), 13 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 096bb8626..db6ceceb2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -581,6 +581,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep cognitoIdentity, LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), + request.getTenantId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index 2ce3b8445..bd1463db6 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -22,6 +22,7 @@ public class LambdaContext implements Context { private final long deadlineTimeInMs; private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; + private final String tenantId; private final LambdaLogger logger; public LambdaContext( @@ -34,6 +35,7 @@ public LambdaContext( CognitoIdentity identity, String functionVersion, String invokedFunctionArn, + String tenantId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -46,6 +48,7 @@ public LambdaContext( this.clientContext = clientContext; this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; + this.tenantId = tenantId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -91,6 +94,10 @@ public int getRemainingTimeInMillis() { return delta > 0 ? delta : 0; } + public String getTenantId() { + return tenantId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java index b98721ebe..f463e7ee5 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java @@ -41,6 +41,7 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) if (lambdaContext != null) { msg.AWSRequestId = lambdaContext.getAwsRequestId(); + msg.tenantId = lambdaContext.getTenantId(); } return msg; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java index 5299bffa5..0ae19961f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StructuredLogMessage.java @@ -12,4 +12,5 @@ class StructuredLogMessage { public String message; public LogLevel level; public String AWSRequestId; + public String tenantId; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java index 7bdc2500e..656945b41 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java @@ -40,6 +40,11 @@ public class InvocationRequest { */ private String cognitoIdentity; + /** + * The tenant ID associated with the request. + */ + private String tenantId; + private byte[] content; public String getId() { @@ -94,6 +99,14 @@ public void setCognitoIdentity(String cognitoIdentity) { this.cognitoIdentity = cognitoIdentity; } + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + public byte[] getContent() { return content; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index 7fe47aa4d..f06796616 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -20,6 +20,7 @@ static jfieldID contentField; static jfieldID clientContextField; static jfieldID cognitoIdentityField; static jfieldID xrayTraceIdField; +static jfieldID tenantIdField; jint JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -41,6 +42,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { xrayTraceIdField = env->GetFieldID(invocationRequestClass , "xrayTraceId", "Ljava/lang/String;"); clientContextField = env->GetFieldID(invocationRequestClass , "clientContext", "Ljava/lang/String;"); cognitoIdentityField = env->GetFieldID(invocationRequestClass , "cognitoIdentity", "Ljava/lang/String;"); + tenantIdField = env->GetFieldID(invocationRequestClass, "tenantId", "Ljava/lang/String;"); return JNI_VERSION; } @@ -106,6 +108,10 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_ CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, cognitoIdentityField, env->NewStringUTF(response.cognito_identity.c_str()))); } + if(response.tenant_id != ""){ + CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, tenantIdField, env->NewStringUTF(response.tenant_id.c_str()))); + } + bytes = reinterpret_cast(response.payload.c_str()); CHECK_EXCEPTION(env, jArray = env->NewByteArray(response.payload.length())); CHECK_EXCEPTION(env, env->SetByteArrayRegion(jArray, 0, response.payload.length(), bytes)); diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h index 94e1e22cb..d7db5f183 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h @@ -61,6 +61,11 @@ struct invocation_request { */ std::chrono::time_point deadline; + /** + * Tenant ID of the current invocation. + */ + std::string tenant_id; + /** * The number of milliseconds left before lambda terminates the current execution. */ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index 91750840f..eeaf0e7b9 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; +static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; enum Endpoints { INIT, @@ -301,6 +302,10 @@ runtime::next_outcome runtime::get_next() req.payload.c_str(), static_cast(req.get_time_remaining().count())); } + + if (resp.has_header(TENANT_ID_HEADER)) { + req.tenant_id = resp.get_header(TENANT_ID_HEADER); + } return next_outcome(req); } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 19744dd51..58880be43 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -18,6 +18,7 @@ public class LambdaContextTest { private static final String INVOKED_FUNCTION_ARN = "invoked-function-arn"; private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; + public static final String TENANT_ID = "tenant-id"; @Test public void getRemainingTimeInMillis() { @@ -54,6 +55,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 8630d5fe6..531e9ca94 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -29,6 +29,25 @@ void testFormattingWithLambdaContext() { null, null, "function-arn", + null, + null + ); + assertFormatsString("test log", LogLevel.WARN, context); + } + + @Test + void testFormattingWithTenantIdInLambdaContext() { + LambdaContext context = new LambdaContext( + 0, + 0, + "request-id", + null, + null, + "function-name", + null, + null, + "function-arn", + "tenant-id", null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -52,6 +71,7 @@ void assert_expected_log_message(StructuredLogMessage result, String message, Lo if (context != null) { assertEquals(context.getAwsRequestId(), result.AWSRequestId); + assertEquals(context.getTenantId(), result.tenantId); } } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java index 1b6f3136a..473e2aef3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.StackElement; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayException; @@ -312,27 +313,62 @@ public void restoreNextWrongStatusCodeTest() { } @Test - public void nextTest() { + public void nextWithoutTenantIdHeaderTest() { try { - MockResponse mockResponse = new MockResponse(); - mockResponse.setResponseCode(HTTP_ACCEPTED); - mockResponse.setHeader("lambda-runtime-aws-request-id", "1234567890"); - mockResponse.setHeader("Content-Type", "application/json"); + MockResponse mockResponse = buildMockResponseForNextInvocation(); mockWebServer.enqueue(mockResponse); - lambdaRuntimeApiClientImpl.nextInvocation(); - RecordedRequest recordedRequest = mockWebServer.takeRequest(); - HttpUrl actualUrl = recordedRequest.getRequestUrl(); - String expectedUrl = "http://" + getHostnamePort() + "/2018-06-01/runtime/invocation/next"; - assertEquals(expectedUrl, actualUrl.toString()); + InvocationRequest invocationRequest = lambdaRuntimeApiClientImpl.nextInvocation(); + verifyNextInvocationRequest(); + assertNull(invocationRequest.getTenantId()); + } catch(Exception e) { + fail(); + } + } + + @Test + public void nextWithTenantIdHeaderTest() { + try { + MockResponse mockResponse = buildMockResponseForNextInvocation(); + String expectedTenantId = "my-tenant-id"; + mockResponse.setHeader("lambda-runtime-aws-tenant-id", expectedTenantId); + mockWebServer.enqueue(mockResponse); + + InvocationRequest invocationRequest = lambdaRuntimeApiClientImpl.nextInvocation(); + verifyNextInvocationRequest(); + assertEquals(expectedTenantId, invocationRequest.getTenantId()); - String actualBody = recordedRequest.getBody().readUtf8(); - assertEquals("", actualBody); } catch(Exception e) { fail(); } } + @Test + public void nextWithEmptyTenantIdHeaderTest() { + try { + MockResponse mockResponse = buildMockResponseForNextInvocation(); + mockResponse.setHeader("lambda-runtime-aws-tenant-id", ""); + mockWebServer.enqueue(mockResponse); + + InvocationRequest invocationRequest = lambdaRuntimeApiClientImpl.nextInvocation(); + verifyNextInvocationRequest(); + assertNull(invocationRequest.getTenantId()); + } catch(Exception e) { + fail(); + } + } + + @Test + public void nextWithNullTenantIdHeaderTest() { + try { + MockResponse mockResponse = buildMockResponseForNextInvocation(); + assertThrows(NullPointerException.class, () -> { + mockResponse.setHeader("lambda-runtime-aws-tenant-id", null); + }); + } catch(Exception e) { + fail(); + } + } @Test public void createUrlMalformedTest() { @@ -376,6 +412,24 @@ public void lambdaReportErrorXRayHeaderTooLongTest() { } } + private MockResponse buildMockResponseForNextInvocation() { + MockResponse mockResponse = new MockResponse(); + mockResponse.setResponseCode(HTTP_ACCEPTED); + mockResponse.setHeader("lambda-runtime-aws-request-id", "1234567890"); + mockResponse.setHeader("Content-Type", "application/json"); + return mockResponse; + } + + private void verifyNextInvocationRequest() throws Exception { + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + HttpUrl actualUrl = recordedRequest.getRequestUrl(); + String expectedUrl = "http://" + getHostnamePort() + "/2018-06-01/runtime/invocation/next"; + assertEquals(expectedUrl, actualUrl.toString()); + + String actualBody = recordedRequest.getBody().readUtf8(); + assertEquals("", actualBody); + } + private String getHostnamePort() { return mockWebServer.getHostName() + ":" + mockWebServer.getPort(); } From 22298613521f624ed75bf9e79dddb12f353b3d9e Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 8 May 2025 20:41:33 +0100 Subject: [PATCH 048/173] Fix EventHandlerLoaderTest failing after PojoSerializerLoaderTest (#542) --- .../lambda/runtime/api/client/PojoSerializerLoaderTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index 7c6e9dcb4..c2c887973 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -7,6 +7,7 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,6 +32,7 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; + @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); From 317b5ada21d3d31b55c948b62b2f978d97c89419 Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 11:31:32 +0100 Subject: [PATCH 049/173] Add tenant id to Context interface (#545) --- .../com/amazonaws/services/lambda/runtime/Context.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index a0850e78c..152765df1 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -100,4 +100,13 @@ public interface Context { */ LambdaLogger getLogger(); + /** + * + * Returns the tenant ID associated with the request. + * + * @return null by default + */ + default String getTenantId() { + return null; + } } From 45ffaeff9b22d0007e13daabc8fb33025e1910de Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 13:32:14 +0100 Subject: [PATCH 050/173] Bump core version to 1.3.0 (#546) --- README.md | 2 +- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fdc08a759..7f4970949 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ public class HandlerStream implements RequestStreamHandler { com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 ``` diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index ebd0566ff..527e7dd46 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 26, 2025 +`1.3.0` +- Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) + ### August 17, 2023 `1.2.3`: - Extended logger interface with level-aware logging backend functions diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 0dd848a96..2b3abc0ba 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 jar AWS Lambda Java Core Library From 5a0f8ad9e9b8ff61ba626e71c4b5de62134e82e4 Mon Sep 17 00:00:00 2001 From: Ramisa Alam <44811300+ramisa2108@users.noreply.github.com> Date: Mon, 26 May 2025 15:23:45 +0100 Subject: [PATCH 051/173] Bump RIC to 2.7.0 (#544) --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7f4970949..b1293c42d 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 8a95e7ded..d49201bd5 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 @@ -160,7 +160,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 6a781b270..ac073ae85 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 21, 2025 +`2.7.0` +- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) + ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index e84cac0da..d4f7fd5e3 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 jar AWS Lambda Java Runtime Interface Client @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.2.3 + 1.3.0 com.amazonaws From f001ae78f6f6f8a76dd260305934951f4d61e5de Mon Sep 17 00:00:00 2001 From: karthikpswamy Date: Tue, 17 Jun 2025 09:43:00 -0700 Subject: [PATCH 052/173] Adding Key/Value Schema metadata attributes for KafkaEvent (#548) Co-authored-by: Karthik Puttaswamy --- .../services/lambda/runtime/events/KafkaEvent.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java index dd051d48f..aa6c00de3 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/KafkaEvent.java @@ -43,6 +43,8 @@ public static class KafkaEventRecord { private String key; private String value; private List> headers; + private SchemaMetadata keySchemaMetadata; + private SchemaMetadata valueSchemaMetadata; } @Data @@ -59,4 +61,13 @@ public String toString() { return topic + "-" + partition; } } + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder(setterPrefix = "with") + public static class SchemaMetadata { + private String schemaId; + private String dataFormat; + } } From 95dc035b2a3200be04eaa48cef6053404250e547 Mon Sep 17 00:00:00 2001 From: karthikpswamy Date: Wed, 18 Jun 2025 02:17:42 -0700 Subject: [PATCH 053/173] Bump aws-lambda-java-events version to 3.16.0 (#549) * Adding Key/Value Schema metadata attributes for KafkaEvent * Bump aws-lambda-java-events version to 3.16.0 --------- Co-authored-by: Karthik Puttaswamy --- README.md | 2 +- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../custom-serialization/fastJson/HelloWorldFunction/pom.xml | 2 +- samples/custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +- samples/custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 10 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b1293c42d..b5153a87f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 87c61f345..43c25d76a 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6c1769751..a4bcd10a0 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 17, 2025 +`3.16.0`: +- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) + ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index f1364e7ab..8799966be 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 6bb4ac8a6..0ab074cce 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7325c72a0..7d3c44246 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index dd3b8e9c5..fd4271824 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index f23214976..2773ef1f1 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index 68e7e81e9..f6ca21bf7 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3d03205d3..56c959244 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 From e20395d6d6b5ec14cf9a868645dc7c1e68660cb8 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 15:57:01 +0100 Subject: [PATCH 054/173] Add sample event --- aws-lambda-java-tests/src/test/resources/connect_event.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/src/test/resources/connect_event.json b/aws-lambda-java-tests/src/test/resources/connect_event.json index a9e04f7f8..b71bf6692 100644 --- a/aws-lambda-java-tests/src/test/resources/connect_event.json +++ b/aws-lambda-java-tests/src/test/resources/connect_event.json @@ -22,7 +22,10 @@ } }, "PreviousContactId": "4ca32fbd-8f92-46af-92a5-6b0f970f0efe", - "Queue": null, + "Queue": { + "Name": "SampleQueue", + "ARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa" + }, "SystemEndpoint": { "Address": "+21234567890", "Type": "TELEPHONE_NUMBER" From 4c3ef97ec05950c00bdfbeeff46c3b6836559dc3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:00:31 +0100 Subject: [PATCH 055/173] Add class to ConnectEvent.java --- .../services/lambda/runtime/events/ConnectEvent.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java index 38547ac2a..e94875614 100644 --- a/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java +++ b/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/ConnectEvent.java @@ -59,7 +59,7 @@ public static class ContactData implements Serializable, Cloneable { private String initiationMethod; private String instanceArn; private String previousContactId; - private String queue; + private Queue queue; private SystemEndpoint systemEndpoint; } @@ -80,4 +80,13 @@ public static class SystemEndpoint implements Serializable, Cloneable { private String address; private String type; } + @Data + @Builder(setterPrefix = "with") + @NoArgsConstructor + @AllArgsConstructor + public static class Queue implements Serializable, Cloneable { + private String name; + private String ARN; + } + } From 8dea6c7534dcc3e495704239daf6e6179153df7d Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:17:18 +0100 Subject: [PATCH 056/173] Add test --- .../services/lambda/runtime/tests/EventLoaderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 86ad73228..752b84e27 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -333,6 +333,14 @@ public void testLoadConnectEvent() { assertThat(contactData.getSystemEndpoint()) .returns("+21234567890",from(ConnectEvent.SystemEndpoint::getAddress)) .returns("TELEPHONE_NUMBER",from(ConnectEvent.SystemEndpoint::getType)); + + assertThat(contactData.getQueue()) + .isNotNull() + .returns("SampleQueue", from(ConnectEvent.Queue::getName)) + .returns("arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa", + from(ConnectEvent.Queue::getARN) + ); + } @Test From 19cdddc13cad041a600b386c2fe756750c0d71f3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Thu, 10 Jul 2025 16:26:48 +0100 Subject: [PATCH 057/173] Add mixin, register it, and register the fields too. --- .../serialization/events/LambdaEventSerializers.java | 7 ++++++- .../serialization/events/mixins/ConnectEventMixin.java | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 4173211e1..3b10b198e 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -118,6 +118,7 @@ public class LambdaEventSerializers { ConnectEventMixin.ContactDataMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", ConnectEventMixin.SystemEndpointMixin.class), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", @@ -170,6 +171,7 @@ public class LambdaEventSerializers { new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", Arrays.asList( @@ -214,7 +216,10 @@ public class LambdaEventSerializers { */ private static final Map NAMING_STRATEGY_MAP = Stream.of( new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategy.PascalCaseStrategy())) + new PropertyNamingStrategy.PascalCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategy.PascalCaseStrategy()) + ) .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); /** diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java index 529a33b39..1645fdaee 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/mixins/ConnectEventMixin.java @@ -65,8 +65,8 @@ public abstract class ContactDataMixin { @JsonProperty("PreviousContactId") abstract void setPreviousContactId(String previousContactId); // needed because Jackson expects "queue" instead of "Queue" - @JsonProperty("Queue") abstract String getQueue(); - @JsonProperty("Queue") abstract void setQueue(String queue); + @JsonProperty("Queue") abstract Map getQueue(); + @JsonProperty("Queue") abstract void setQueue(Map queue); // needed because Jackson expects "systemEndpoint" instead of "SystemEndpoint" @JsonProperty("SystemEndpoint") abstract Map getSystemEndpoint(); @@ -95,4 +95,9 @@ public abstract class SystemEndpointMixin { @JsonProperty("Type") abstract String getType(); @JsonProperty("Type") abstract void setType(String type); } + + public abstract class QueueMixin { + @JsonProperty("Name") abstract String getName(); + @JsonProperty("Name") abstract void setName(String name); + } } From a9e47682bd11359cf2288194ec7600adc55ef8a0 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:08:43 +0100 Subject: [PATCH 058/173] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?= =?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?= =?UTF-8?q?pendencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6 - Bump aws-lambda-java-events from 3.16.0 to 3.16.1 - Update aws-lambda-java-tests dependencies to use new versions - Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2→1.1.6 - Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2→3.16.1 --- aws-lambda-java-events-sdk-transformer/pom.xml | 2 +- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6a2b1735c..e67ff81c3 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -63,7 +63,7 @@ com.amazonaws aws-lambda-java-events - 3.11.2 + 3.16.1 provided diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 8799966be..925273e9b 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index d4f7fd5e3..c82890b0f 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -66,7 +66,7 @@ com.amazonaws aws-lambda-java-serialization - 1.1.2 + 1.1.6 diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 07ccecc8c..2f8e76138 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.1.5 + 1.1.6 jar AWS Lambda Java Runtime Serialization diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 0ab074cce..aa028769e 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -40,12 +40,12 @@ com.amazonaws aws-lambda-java-serialization - 1.1.5 + 1.1.6 com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 org.junit.jupiter From 70e2d8b83dd6ffd2b1fe42f386b7bdf9a8d3f1c6 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:26:46 +0100 Subject: [PATCH 059/173] Fix runtime interface client workflows to use local serialization dependency Add local build step for aws-lambda-java-serialization before building runtime interface client. ## Why This Fix is Needed The runtime interface client depends on aws-lambda-java-serialization version 1.1.6, but this version doesn't exist in Maven Central yet. By building and installing the serialization package locally first, we ensure: 1. The correct version (1.1.6) is available in the local Maven repository 2. The runtime interface client build won't fail looking for a non-existent version on Maven Central 3. The workflow tests the actual code changes together ## Changes Made - runtime-interface-client_merge_to_main.yml: Added local serialization build step - runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs This ensures CI/CD pipeline works correctly with the new dependency versions. --- .../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++ .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index e07b191e1..88f8afde2 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -47,6 +47,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 33c6df50b..94c034562 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -23,6 +23,10 @@ jobs: java-version: 8 distribution: corretto + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr @@ -51,6 +55,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build From 37309c0d9f60bd2dfc80bd760c1fa9d05d80bba9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:20:22 +0000 Subject: [PATCH 060/173] Bump org.apache.commons:commons-lang3 in /aws-lambda-java-tests Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index aa028769e..f73bee5f1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -65,7 +65,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.18.0 From d9f3a9c6f6ffd0074427e7cd7fcd35b84b58dd72 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:09:19 +0100 Subject: [PATCH 061/173] Update sdk transformer pom.xml patch version for serialization and events changes --- aws-lambda-java-events-sdk-transformer/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index e67ff81c3..9b6afece1 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events-sdk-transformer - 3.1.0 + 3.1.1 jar AWS Lambda Java Events SDK Transformer Library From e9320fa87361e9fb757fbf19748129d0cac8bcbd Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:12:48 +0100 Subject: [PATCH 062/173] Update ric pom.xml --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c82890b0f..979f4b7ba 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.7.1 jar AWS Lambda Java Runtime Interface Client From 8147e7afa30057d63733d79aadc2630da51094df Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:14:19 +0100 Subject: [PATCH 063/173] Update test pom.xml --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index f73bee5f1..da07133c1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.1 + 1.1.2 jar AWS Lambda Java Tests From e54cf69b3751868b1adaf4824bb82744ed1fdfd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:58:32 +0100 Subject: [PATCH 064/173] Bump actions/checkout from 4 to 5 (#559) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/repo-sync.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 267d901c9..c8064513c 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 66f6b2bfe..285848a9f 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 04ab53a50..b3b360b45 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 7ae54cbe1..03718e602 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index db9fc225e..880320953 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,7 +22,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK uses: actions/setup-java@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index c24c48d72..b2700e088 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index a28bca886..1b818014a 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 25f05029a..300341c1f 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -16,7 +16,7 @@ jobs: env: IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 if: ${{ env.IS_CONFIGURED == 'true' }} - uses: repo-sync/github-sync@v2 name: Sync repo to branch diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 88f8afde2..8909d56bf 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 94c034562..c090609c3 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -15,7 +15,7 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -36,7 +36,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 8346b7c2f..2b5e7833f 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -18,7 +18,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: @@ -42,7 +42,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 From 640b7665b429e98d6b4941d39f24c2e43a4db25c Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi <117035062+AntoniaSzecsi@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:15:19 +0100 Subject: [PATCH 065/173] Enabling Local Testing with RIE and Improving Tests Coverability (#560) * Add local testing support with RIE * Improve code test coverage from 0.45 to 0.5 * Replace the hardcoded versions with RELEASE versions * Replace Dockerfile base image with image that has RIE preinstalled * Change from mvn central artifacts to locally built ones * Update from java 11 to java 21 and remove duplicate entries from pom * Clean up test coverage configuration * Update README to reflect changes --------- Co-authored-by: AntoniaSzecsi --- .../Dockerfile.rie | 8 ++++ .../Makefile | 6 ++- .../README.md | 43 +++++++++++++++++ .../pom.xml | 18 ++++++++ .../scripts/test-rie.sh | 46 +++++++++++++++++++ .../test-handlers/EchoHandler.java | 20 ++++++++ 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 aws-lambda-java-runtime-interface-client/Dockerfile.rie create mode 100755 aws-lambda-java-runtime-interface-client/scripts/test-rie.sh create mode 100644 aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java diff --git a/aws-lambda-java-runtime-interface-client/Dockerfile.rie b/aws-lambda-java-runtime-interface-client/Dockerfile.rie new file mode 100644 index 000000000..66a01c834 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/Dockerfile.rie @@ -0,0 +1,8 @@ +FROM public.ecr.aws/lambda/java:21 + +COPY target/aws-lambda-java-runtime-interface-client-*.jar ${LAMBDA_TASK_ROOT}/ +COPY target/aws-lambda-java-core-*.jar ${LAMBDA_TASK_ROOT}/ +COPY target/aws-lambda-java-serialization-*.jar ${LAMBDA_TASK_ROOT}/ +COPY test-handlers/EchoHandler.class ${LAMBDA_TASK_ROOT}/ + +CMD ["EchoHandler::handleRequest"] \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index b3a204213..6c3a268fb 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -65,6 +65,10 @@ publish: test-publish: ./ric-dev-environment/test-platform-specific-jar-snapshot.sh +.PHONY: test-rie +test-rie: + ./scripts/test-rie.sh "EchoHandler::handleRequest" + define HELP_MESSAGE Usage: $ make [TARGETS] @@ -74,5 +78,5 @@ TARGETS dev Run all development tests after a change. pr Perform all checks before submitting a Pull Request. test Run the Unit tests. - + test-rie Build and test RIC locally with Lambda Runtime Interface Emulator. (Requires building the project first) endef diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index d49201bd5..67a5972d6 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -138,6 +138,49 @@ This command invokes the function running in the container image and returns a r *Alternately, you can also include RIE as a part of your base image. See the AWS documentation on how to [Build RIE into your base image](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-alternative).* +### Automated Local Testing + +For developers working on this runtime interface client, we provide an automated testing script that handles RIE setup, dependency management, and Docker orchestration. + +*Prerequisites:* +- Build the project first: `mvn clean install` +- Docker must be installed and running + +*To run automated tests:* + +```shell script +make test-rie +``` + +This single command will: +- Automatically download required dependencies (aws-lambda-java-core, aws-lambda-java-serialization) +- Build a Docker image with RIE pre-installed +- Compile and run a test Lambda function (EchoHandler) +- Execute the function and validate the response +- Clean up containers automatically + +The test uses a simple EchoHandler that returns the input event, making it easy to verify the runtime interface client is working correctly. + +## Test Coverage + +This project uses JaCoCo for code coverage analysis. To exclude classes from JaCoCo coverage, add them to the `jacoco-maven-plugin` configuration: + +```xml + + org.jacoco + jacoco-maven-plugin + + + **/*Exception.class + **/dto/*.class + **/YourClassName.class + + + +``` + +This project excludes by default: exceptions, interfaces, DTOs, constants, and runtime-only classes. + ### Troubleshooting While running integration tests, you might encounter the Docker Hub rate limit error with the following body: diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 979f4b7ba..84c6d816c 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -214,6 +214,24 @@ org.jacoco jacoco-maven-plugin ${jacoco.maven.plugin.version} + + + + **/*Exception.class + + **/Resource.class + + **/dto/*.class + + **/ReservedRuntimeEnvironmentVariables.class + **/RapidErrorType.class + + **/FrameType.class + **/StructuredLogMessage.class + + **/AWSLambda.class + + default-prepare-agent diff --git a/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh new file mode 100755 index 000000000..b69c967a1 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/scripts/test-rie.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +SERIALIZATION_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-serialization" + +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar >/dev/null 2>&1; then + echo "RIC jar not found. Please build the project first with 'mvn package'." + exit 1 +fi + +IMAGE_TAG="java-ric-rie-test" + +HANDLER="${1:-EchoHandler::handleRequest}" + +echo "Starting RIE test setup for Java..." + +# Build local dependencies if not present +CORE_ROOT="$(dirname "$PROJECT_ROOT")/aws-lambda-java-core" +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar >/dev/null 2>&1; then + echo "Building local aws-lambda-java-core..." + (cd "$CORE_ROOT" && mvn package -DskipTests) + cp "$CORE_ROOT"/target/aws-lambda-java-core-*.jar "$PROJECT_ROOT/target/" +fi + +if ! ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar >/dev/null 2>&1; then + echo "Building local aws-lambda-java-serialization..." + (cd "$SERIALIZATION_ROOT" && mvn package -DskipTests) + cp "$SERIALIZATION_ROOT"/target/aws-lambda-java-serialization-*.jar "$PROJECT_ROOT/target/" +fi + +echo "Compiling EchoHandler..." +javac -source 21 -target 21 -cp "$(ls "$PROJECT_ROOT"/target/aws-lambda-java-runtime-interface-client-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-core-*.jar):$(ls "$PROJECT_ROOT"/target/aws-lambda-java-serialization-*.jar)" \ + -d "$PROJECT_ROOT/test-handlers/" "$PROJECT_ROOT/test-handlers/EchoHandler.java" + +echo "Building test Docker image..." +docker build -t "$IMAGE_TAG" -f "$PROJECT_ROOT/Dockerfile.rie" "$PROJECT_ROOT" + +echo "Starting test container on port 9000..." +echo "" +echo "In another terminal, invoke with:" +echo "curl -s -X POST -H 'Content-Type: application/json' \"http://localhost:9000/2015-03-31/functions/function/invocations\" -d '{\"message\":\"test\"}'" +echo "" + +exec docker run -it -p 9000:8080 -e _HANDLER="$HANDLER" "$IMAGE_TAG" \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java b/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java new file mode 100644 index 000000000..cb324e7f7 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/test-handlers/EchoHandler.java @@ -0,0 +1,20 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import java.util.Map; +import java.util.HashMap; + +public class EchoHandler implements RequestHandler, Map> { + + @Override + public Map handleRequest(Map event, Context context) { + context.getLogger().log("Processing event: " + event); + + Map response = new HashMap<>(event); + response.put("timestamp", System.currentTimeMillis()); + response.put("requestId", context.getAwsRequestId()); + response.put("functionName", context.getFunctionName()); + response.put("remainingTimeInMillis", context.getRemainingTimeInMillis()); + + return response; + } +} \ No newline at end of file From 434185b8f7cbd2033dcfebf52d4feb33004597d8 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 5 Sep 2025 17:55:34 +0100 Subject: [PATCH 066/173] Make Trace ID accessible through Context & aws-lambda-java-core version bump to 1.4 (#563) Co-authored-by: Mohammed Ehab --- .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 + .../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++ .../runtime/api/client/api/LambdaContextTest.java | 3 ++- .../api/client/logging/JsonLogFormatterTest.java | 2 ++ .../test/integration/codebuild/buildspec.os.alpine.yml | 1 + .../codebuild/buildspec.os.amazoncorretto.yml | 1 + .../codebuild/buildspec.os.amazonlinux.1.yml | 1 + .../codebuild/buildspec.os.amazonlinux.2.yml | 1 + .../test/integration/codebuild/buildspec.os.centos.yml | 1 + .../test/integration/codebuild/buildspec.os.debian.yml | 1 + .../test/integration/codebuild/buildspec.os.ubuntu.yml | 1 + 16 files changed, 43 insertions(+), 3 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index c090609c3..35c6ca06b 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -22,6 +22,10 @@ jobs: with: java-version: 8 distribution: corretto + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization @@ -54,6 +58,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index 527e7dd46..aebc8ecd9 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### September 3, 2025 +`1.4.0` +- Getter support for x-ray trace ID through the Context object + ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 2b3abc0ba..5c1e1d02d 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index 152765df1..ed9311a11 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,4 +109,14 @@ public interface Context { default String getTenantId() { return null; } + + /** + * + * Returns the X-Ray trace ID associated with the request. + * + * @return null by default + */ + default String getXrayTraceId() { + return null; + } } diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 84c6d816c..4c5f73575 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 com.amazonaws diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index db6ceceb2..2876499ec 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -582,6 +582,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), + request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index bd1463db6..20b77262d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,6 +23,7 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; + private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -36,6 +37,7 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, + String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -49,6 +51,7 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; + this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -98,6 +101,10 @@ public String getTenantId() { return tenantId; } + public String getXrayTraceId() { + return xrayTraceId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 58880be43..f7da76198 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,6 +19,7 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; + public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 531e9ca94..91ce9d2a3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, + null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", + "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index cdc27a655..2a71cb1b0 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -43,6 +43,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index 67dd7617d..db8bf2ba0 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -42,6 +42,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml index 04c486a88..e3773cf82 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml @@ -37,6 +37,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index 8222bb41a..a9836fc6f 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -41,6 +41,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml index d718c2647..74d12b01d 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml @@ -41,6 +41,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml index d2772fbfc..222d14a36 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml @@ -42,6 +42,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml index 2a90017b3..ce153c547 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml @@ -44,6 +44,7 @@ phases: # Install events (dependency of serialization) - (cd aws-lambda-java-events && mvn install) # Install serialization (dependency of RIC) + - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - (cd aws-lambda-java-runtime-interface-client && mvn install) - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) From 5c6d3fa10fce436a31889fc9b6d99951746cee16 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:42:49 +0100 Subject: [PATCH 067/173] Migrate to Maven Central Portal from OSSRH for RIC & Core Packages. RIC Version Bump to 2.8.4 (#565) Migrate to Maven Central Portal Publishing instead of OSSRH for aws-lambda-java-core and aws-lambda-java-runtime-interface-client For Other Packages, they we will be following the same approach in aws-lambda-java-core. Only aws-lambda-java-runtime-interface-client is different since, per deployment, it has an uber-jar and platform-specific jars. --------- Co-authored-by: Mohammed Ehab --- aws-lambda-java-core/pom.xml | 17 ++----- .../pom.xml | 50 ++++++++++++++++--- .../test/integration/test-handler/pom.xml | 3 +- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 5c1e1d02d..cca9d0cdf 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -36,13 +36,6 @@ 1.8 - - - sonatype-nexus-staging - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - dev @@ -115,14 +108,12 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false + central diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 4c5f73575..c854fabcd 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.1 + 2.8.4 jar AWS Lambda Java Runtime Interface Client @@ -365,16 +365,52 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - sonatype-nexus-staging - https://aws.oss.sonatype.org/ - false + central + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + attach-platform-artifacts + package + + attach-artifact + + + + + ${project.build.directory}/${project.build.finalName}-linux-x86_64.jar + jar + linux-x86_64 + + + ${project.build.directory}/${project.build.finalName}-linux-aarch_64.jar + jar + linux-aarch_64 + + + ${project.build.directory}/${project.build.finalName}-linux_musl-x86_64.jar + jar + linux_musl-x86_64 + + + ${project.build.directory}/${project.build.finalName}-linux_musl-aarch_64.jar + jar + linux_musl-aarch_64 + + + + + + diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 2e240fe34..46d191a74 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.8.4 @@ -50,4 +50,3 @@ - From 199c9621eaa214720f099e0c9cb9b5496d8b813e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 24 Mar 2025 14:56:05 +0000 Subject: [PATCH 068/173] misc: merge from public (#80) --- .github/workflows/runtime-interface-client_pr.yml | 8 -------- .github/workflows/samples.yml | 2 +- README.md | 4 ++-- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../runtime/api/client/PojoSerializerLoaderTest.java | 2 -- .../test/integration/test-handler/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/gson/HelloWorldFunction/pom.xml | 2 +- .../custom-serialization/moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 15 insertions(+), 33 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 35c6ca06b..cbc5b5ab1 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -58,14 +58,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Build and install core dependency locally - working-directory: ./aws-lambda-java-core - run: mvn clean install - - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2b5e7833f..6d63f423e 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -42,7 +42,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 diff --git a/README.md b/README.md index b5153a87f..7f4970949 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 ``` diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 43c25d76a..87c61f345 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index a4bcd10a0..6c1769751 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### June 17, 2025 -`3.16.0`: -- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) - ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 925273e9b..f1364e7ab 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..368ab710a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.6.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..6a781b270 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### May 21, 2025 -`2.7.0` -- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) - ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..fde515fda 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index c2c887973..7c6e9dcb4 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -7,7 +7,6 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,7 +31,6 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; - @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 46d191a74..5304d44b9 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.6.0 diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index da07133c1..9e3419c69 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.15.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7d3c44246..7325c72a0 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index fd4271824..dd3b8e9c5 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index 2773ef1f1..f23214976 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index f6ca21bf7..68e7e81e9 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 56c959244..3d03205d3 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.15.0 From a9c7fd1eea83423ed8d5887d31c588b433146be2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 7 May 2025 11:00:21 +0100 Subject: [PATCH 069/173] Multi-concurrency mode for the Runtime Interface Client and thread_local C++ Curl Client (#82) Multi Concurrent RIC and Enable Curl Multi Concurrent Requests --- .github/dependabot.yml | 4 + .../lambda/runtime/api/client/AWSLambda.java | 69 +++-- .../api/client/EventHandlerLoader.java | 4 +- .../api/client/PojoSerializerLoader.java | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 13 + .../api/client/util/ConcurrencyConfig.java | 55 ++++ .../include/aws/lambda-runtime/runtime.h | 1 - .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 87 ++++--- .../runtime/api/client/AWSLambdaTest.java | 238 ++++++++++++++++++ .../client/util/ConcurrencyConfigTest.java | 94 +++++++ 10 files changed, 502 insertions(+), 65 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java create mode 100644 aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3722537ae..34d287eec 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,9 @@ version: 2 updates: + - package-ecosystem: "maven" + directory: "/aws-lambda-java-runtime-interface" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/" diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 2eeb14e3d..fdd090077 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream; import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil; import com.amazonaws.services.lambda.runtime.logging.LogFormat; @@ -35,6 +36,8 @@ import java.net.URLClassLoader; import java.security.Security; import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -49,8 +52,8 @@ */ public class AWSLambda { - protected static URLClassLoader customerClassLoader; - + private static URLClassLoader customerClassLoader; + private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; private static final String JAVA_SECURITY_PROPERTIES = "java.security.properties"; @@ -68,9 +71,7 @@ public class AWSLambda { private static final String INIT_TYPE_SNAP_START = "snap-start"; private static final String AWS_LAMBDA_INITIALIZATION_TYPE = System.getenv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_INITIALIZATION_TYPE); - - private static LambdaRuntimeApiClient runtimeClient; - + static { // Override the disabledAlgorithms setting to match configuration for openjdk8-u181. // This is to keep DES ciphers around while we deploying security updates. @@ -137,15 +138,13 @@ private static LambdaRequestHandler findRequestHandler(final String handlerStrin return requestHandler; } - private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger) throws ClassNotFoundException, IOException { + private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws ClassNotFoundException, IOException { UnsafeUtil.disableIllegalAccessWarning(); System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8")); System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8")); setupRuntimeLogger(lambdaLogger); - runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); - String taskRoot = System.getProperty("user.dir"); String libRoot = "/opt/java"; // Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes @@ -167,13 +166,13 @@ private static LambdaRequestHandler getLambdaRequestHandlerObject(String handler } if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) { - onInitComplete(lambdaLogger); + onInitComplete(lambdaLogger, runtimeClient); } return requestHandler; } - public static void setupRuntimeLogger(LambdaLogger lambdaLogger) + private static void setupRuntimeLogger(LambdaLogger lambdaLogger) throws ClassNotFoundException { ReflectUtil.setStaticField( Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"), @@ -213,10 +212,11 @@ private static LogSink createLogSink() { } public static void main(String[] args) throws Throwable { - try (LambdaContextLogger logger = initLogger()) { - LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], logger); - startRuntimeLoop(lambdaRequestHandler, logger); - + try (LambdaContextLogger lambdaLogger = initLogger()) { + LambdaRuntimeApiClient runtimeClient = new LambdaRuntimeApiClientImpl(LambdaEnvironment.RUNTIME_API); + LambdaRequestHandler lambdaRequestHandler = getLambdaRequestHandlerObject(args[0], lambdaLogger, runtimeClient); + ConcurrencyConfig concurrencyConfig = new ConcurrencyConfig(lambdaLogger); + startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); } catch (IOException | ClassNotFoundException t) { throw new Error(t); } @@ -232,7 +232,38 @@ private static LambdaContextLogger initLogger() { return logger; } - private static void startRuntimeLoop(LambdaRequestHandler requestHandler, LambdaContextLogger lambdaLogger) throws Throwable { + private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { + executorService.submit(() -> { + try { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } catch (Exception e) { + lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + }); + } + + protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ConcurrencyConfig concurrencyConfig, LambdaRuntimeApiClient runtimeClient) throws Exception { + if (concurrencyConfig.isMultiConcurrent()) { + lambdaLogger.log(concurrencyConfig.getConcurrencyConfigMessage(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.INFO : LogLevel.UNDEFINED); + ExecutorService platformThreadExecutor = Executors.newFixedThreadPool(concurrencyConfig.getNumberOfPlatformThreads()); + try { + for (int i = 0; i < concurrencyConfig.getNumberOfPlatformThreads(); i++) { + startRuntimeLoopWithExecutor(lambdaRequestHandler, lambdaLogger, platformThreadExecutor, runtimeClient); + } + } finally { + platformThreadExecutor.shutdown(); + while (true) { + if (platformThreadExecutor.isTerminated()) { + break; + } + } + } + } else { + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + } + } + + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { boolean shouldExit = false; while (!shouldExit) { UserFault userFault = null; @@ -245,7 +276,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda ByteArrayOutputStream payload; try { - payload = requestHandler.call(request); + payload = lambdaRequestHandler.call(request); runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); // clear interrupted flag in case if it was set by user's code Thread.interrupted(); @@ -275,7 +306,7 @@ private static void startRuntimeLoop(LambdaRequestHandler requestHandler, Lambda } } - static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOException { + private static void onInitComplete(final LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws IOException { try { Core.getGlobalContext().beforeCheckpoint(null); runtimeClient.restoreNext(); @@ -303,4 +334,8 @@ private static void logExceptionCloudWatch(LambdaContextLogger lambdaLogger, Exc UserFault userFault = UserFault.makeUserFault(exc, true); lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } + + protected static URLClassLoader getCustomerClassLoader() { + return customerClassLoader; + } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 2876499ec..8fab27fed 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -116,7 +116,7 @@ private static PojoSerializer getSerializer(Platform platform, Type type if (type instanceof Class) { Class clazz = ((Class) type); if (LambdaEventSerializers.isLambdaSupportedEvent(clazz.getName())) { - return LambdaEventSerializers.serializerFor(clazz, AWSLambda.customerClassLoader); + return LambdaEventSerializers.serializerFor(clazz, AWSLambda.getCustomerClassLoader()); } } // else platform dependent (Android uses GSON but all other platforms use Jackson) @@ -533,7 +533,7 @@ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStream private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { - Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.customerClassLoader, log4jContextClassName); + Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); log4jContextPutMethod.call("AWSRequestId", request.getId()); } catch (Exception e) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java index daea5911f..da37f7ca7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoader.java @@ -28,7 +28,7 @@ private static CustomPojoSerializer loadSerializer() return customPojoSerializer; } - ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.customerClassLoader); + ServiceLoader loader = ServiceLoader.load(CustomPojoSerializer.class, AWSLambda.getCustomerClassLoader()); Iterator serializers = loader.iterator(); if (!serializers.hasNext()) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 7500a4943..2d32d4048 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -106,4 +106,17 @@ public interface ReservedRuntimeEnvironmentVariables { * The environment's time zone (UTC). The execution environment uses NTP to synchronize the system clock. */ String TZ = "TZ"; + + /** + * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". + * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. + */ + String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; + + /* + * Used to set the required number of concurrent runtime loops, + * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", + * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + */ + String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java new file mode 100644 index 000000000..db1f6c218 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -0,0 +1,55 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +public class ConcurrencyConfig { + private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private final int numberOfPlatformThreads; + private final boolean isConcurrencyEnabled; + + public ConcurrencyConfig(LambdaContextLogger logger) { + this(logger, new EnvReader()); + } + + public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { + this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); + int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; + + if (this.isConcurrencyEnabled) { + try { + int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { + platformThreads = readNumOfPlatformThreads; + } else { + throw new IllegalArgumentException(); + } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + } + } + + this.numberOfPlatformThreads = platformThreads; + } + + public String getConcurrencyConfigMessage() { + return String.format("Starting %d concurrent function handler threads.", this.numberOfPlatformThreads); + } + + public boolean isMultiConcurrent() { + return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + } + + public int getNumberOfPlatformThreads() { + return numberOfPlatformThreads; + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h index d7db5f183..c4868c1ba 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h @@ -172,7 +172,6 @@ class runtime { private: std::string const m_user_agent_header; std::array const m_endpoints; - CURL* const m_curl_handle; }; inline std::chrono::milliseconds invocation_request::get_time_remaining() const diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index eeaf0e7b9..e713da985 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,7 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; -static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; +thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { INIT, @@ -163,63 +163,62 @@ runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cp runtime::runtime(std::string const& endpoint, std::string const& user_agent) : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", endpoint + "/2018-06-01/runtime/invocation/next", - endpoint + "/2018-06-01/runtime/invocation/"}}, - m_curl_handle(curl_easy_init()) + endpoint + "/2018-06-01/runtime/invocation/"}} { - if (!m_curl_handle) { + if (!lambda_runtime::m_curl_handle) { logging::log_error(LOG_TAG, "Failed to acquire curl easy handle for next."); } } runtime::~runtime() { - curl_easy_cleanup(m_curl_handle); + curl_easy_cleanup(lambda_runtime::m_curl_handle); } void runtime::set_curl_next_options() { // lambda freezes the container when no further tasks are available. The freezing period could be longer than the // request timeout, which causes the following get_next request to fail with a timeout error. - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } void runtime::set_curl_post_result_options() { - curl_easy_reset(m_curl_handle); - curl_easy_setopt(m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_reset(lambda_runtime::m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L); - curl_easy_setopt(m_curl_handle, CURLOPT_READFUNCTION, read_data); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_POST, 1L); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READFUNCTION, read_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); #ifndef NDEBUG - curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); #endif } @@ -227,15 +226,15 @@ runtime::next_outcome runtime::get_next() { http::response resp; set_curl_next_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); curl_slist* headers = nullptr; headers = curl_slist_append(headers, m_user_agent_header.c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); logging::log_debug(LOG_TAG, "Completed request to %s", m_endpoints[Endpoints::NEXT].c_str()); curl_slist_free_all(headers); @@ -247,13 +246,13 @@ runtime::next_outcome runtime::get_next() { long resp_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); resp.set_response_code(static_cast(resp_code)); } { char* content_type = nullptr; - curl_easy_getinfo(m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); resp.set_content_type(content_type); } @@ -327,7 +326,7 @@ runtime::post_outcome runtime::do_post( invocation_response const& handler_response) { set_curl_post_result_options(); - curl_easy_setopt(m_curl_handle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); curl_slist* headers = nullptr; @@ -348,11 +347,11 @@ runtime::post_outcome runtime::do_post( std::pair ctx{payload, 0}; aws::http::response resp; - curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); - curl_easy_setopt(m_curl_handle, CURLOPT_READDATA, &ctx); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); - CURLcode curl_code = curl_easy_perform(m_curl_handle); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READDATA, &ctx); + curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); + CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); curl_slist_free_all(headers); if (curl_code != CURLE_OK) { @@ -366,7 +365,7 @@ runtime::post_outcome runtime::do_post( } long http_response_code; - curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); + curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java new file mode 100644 index 000000000..4412214e9 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -0,0 +1,238 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; +import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class AWSLambdaTest { + + private static class SampleHandler implements RequestHandler, String> { + public static final String ADD_ENTRY_TO_MAP_ID_OP_MODE = "ADD_ENTRY_TO_MAP_ID"; + public static final String FAIL_IMMEDIATELY_OP_MODE = "FAIL_IMMEDIATELY"; + + public static final int nOfIterations = 10; + public static final int perIterationDelayMS = 10; + public static Map hashMap = new ConcurrentHashMap(); + public static AtomicInteger globalCounter = new AtomicInteger(); + + public static void resetStaticFields() { + hashMap.clear(); + globalCounter = new AtomicInteger(); + } + + private static void addEntryToMapImplementation(String name) { + int i = 0; + while (i++ < nOfIterations) { + hashMap.put(name, hashMap.getOrDefault(name, 0) + 1); + globalCounter.incrementAndGet(); + try { + Thread.sleep(perIterationDelayMS); + } catch (InterruptedException e) { + } + } + } + + @Override + public String handleRequest(Map event, Context context) { + // Thread.currentThread().getId() instead of Thread.currentThread().getName() when upgrading JAVA + String name = "Thread " + Thread.currentThread().getName(); + String opMode = event.get("id"); + + switch (opMode) { + case ADD_ENTRY_TO_MAP_ID_OP_MODE: + addEntryToMapImplementation(name); + break; + case FAIL_IMMEDIATELY_OP_MODE: + String[] sArr = {}; + return sArr[1]; + default: + break; + } + + return name; + } + } + + @Mock + private LambdaRuntimeApiClientImpl runtimeClient; + + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Mock + private ConcurrencyConfig concurrencyConfig; + + private LambdaRequestHandler lambdaRequestHandler = new LambdaRequestHandler() { + private SampleHandler sHandler = new SampleHandler(); + + @Override + public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + HashMap eventMap = new HashMap(); + eventMap.put("id", request.getId()); + sHandler.handleRequest(eventMap, null); + return new ByteArrayOutputStream(); + } + }; + + private static InvocationRequest getFakeInvocationRequest(String id) { + InvocationRequest request = new InvocationRequest(); + request.setId(id); + request.setDeadlineTimeInMs(Long.MAX_VALUE); + request.setContent("".getBytes()); + return request; + } + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + SampleHandler.resetStaticFields(); + } + + /* + * com.amazonaws.services.lambda.runtime.api.client.util.SampleHandler contains static fields. These fields are expected to be shared if initialization is behaving as expected. + * After execution of the Runtime loops, we should see that the SampleHandler.globalCounter has been acted on by all the threads. + * The concurrent hashmap in SampleHandler.hashMap should also have all the correct count of Threads that ran. + * IMPORTANT: This test fails through only timeout. + */ + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreads() throws Throwable { + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + // Hashmap keys should equal the number of threads (runtime loops). + assertEquals(4, SampleHandler.hashMap.size()); + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(7 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(4); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(null) + .thenReturn(null) + .thenReturn(null); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. + verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(2, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithFailures() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + InvocationRequest userFaultRequest = mock(InvocationRequest.class); + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } +} \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java new file mode 100644 index 000000000..bbb43cd14 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -0,0 +1,94 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package com.amazonaws.services.lambda.runtime.api.client.util; + +import com.amazonaws.services.lambda.runtime.api.client.ReservedRuntimeEnvironmentVariables; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ConcurrencyConfigTest { + @Mock + private LambdaContextLogger lambdaLogger; + + @Mock + private EnvReader envReader; + + @Test + void testDefaultConfiguration() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(4, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testInvalidPlatformThreadsConfig() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testExceedingPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); + assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigMessage() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + String expectedMessage = "Starting 4 concurrent function handler threads."; + assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); + assertEquals(true, config.isMultiConcurrent()); + } + + @Test + void testGetConcurrencyConfigWithNoConcurrency() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); + } +} From 2769f83b79ce0a83a388468c831c77bd79863b18 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 8 May 2025 13:29:35 +0100 Subject: [PATCH 070/173] Merge from Public Repo (#83) * Merge from Public Repo * Fix Failing Tests --------- Co-authored-by: Mohammed Ehab --- .../workflows/runtime-interface-client_merge_to_main.yml | 6 +----- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 - .../lambda/runtime/api/client/api/LambdaContext.java | 7 ------- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 1 + .../runtime/api/client/PojoSerializerLoaderTest.java | 2 ++ .../lambda/runtime/api/client/api/LambdaContextTest.java | 3 +-- .../runtime/api/client/logging/JsonLogFormatterTest.java | 2 -- 7 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 8909d56bf..e07b191e1 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -47,10 +47,6 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index 8fab27fed..af146fd93 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -582,7 +582,6 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), - request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index 20b77262d..bd1463db6 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,7 +23,6 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; - private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -37,7 +36,6 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, - String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -51,7 +49,6 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; - this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -101,10 +98,6 @@ public String getTenantId() { return tenantId; } - public String getXrayTraceId() { - return xrayTraceId; - } - public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index e713da985..3897d37db 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; +static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java index 7c6e9dcb4..4ebcf5d7e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/PojoSerializerLoaderTest.java @@ -8,6 +8,7 @@ import com.amazonaws.services.lambda.runtime.CustomPojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -31,6 +32,7 @@ class PojoSerializerLoaderTest { @Mock private CustomPojoSerializer mockSerializer; + @AfterEach @BeforeEach void setUp() throws Exception { resetStaticFields(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index f7da76198..58880be43 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,7 +19,6 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; - public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -56,6 +55,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 91ce9d2a3..531e9ca94 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,7 +30,6 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, - null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -49,7 +48,6 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", - "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From a66ed4bdd0a461f486253c1f9adb9f20f5783f86 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:17:36 +0100 Subject: [PATCH 071/173] Support Multiconcurrent Logging (#86) * Support Multiconcurrent Logging --------- Co-authored-by: Mohammed Ehab --- .../api/client/logging/JsonLogFormatter.java | 16 ++++--- .../api/client/logging/StdOutLogSink.java | 2 +- .../logging/AbstractLambdaLoggerTest.java | 43 ++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java index f463e7ee5..f1051a216 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatter.java @@ -22,7 +22,7 @@ public class JsonLogFormatter implements LogFormatter { withZone(ZoneId.of("UTC")); private final PojoSerializer serializer = GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); - private LambdaContext lambdaContext; + private ThreadLocal lambdaContext = new ThreadLocal<>(); @Override public String format(String message, LogLevel logLevel) { @@ -39,10 +39,12 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) msg.message = message; msg.level = logLevel; - if (lambdaContext != null) { - msg.AWSRequestId = lambdaContext.getAwsRequestId(); - msg.tenantId = lambdaContext.getTenantId(); + LambdaContext lambdaContextForCurrentThread = lambdaContext.get(); + if (lambdaContextForCurrentThread != null) { + msg.AWSRequestId = lambdaContextForCurrentThread.getAwsRequestId(); + msg.tenantId = lambdaContextForCurrentThread.getTenantId(); } + return msg; } @@ -53,6 +55,10 @@ private StructuredLogMessage createLogMessage(String message, LogLevel logLevel) */ @Override public void setLambdaContext(LambdaContext context) { - this.lambdaContext = context; + if (context == null) { + lambdaContext.remove(); + } else { + lambdaContext.set(context); + } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java index 873e6fde5..90e7d39c2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/logging/StdOutLogSink.java @@ -15,7 +15,7 @@ public void log(byte[] message) { log(LogLevel.UNDEFINED, LogFormat.TEXT, message); } - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { try { System.out.write(message); } catch (IOException e) { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index baeb4c242..151e702af 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -5,10 +5,15 @@ import com.amazonaws.services.lambda.runtime.logging.LogFormat; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import com.amazonaws.lambda.thirdparty.org.json.JSONObject; import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; import com.amazonaws.services.lambda.runtime.logging.LogLevel; @@ -20,12 +25,12 @@ public TestSink() { } @Override - public void log(byte[] message) { + public synchronized void log(byte[] message) { messages.add(message); } @Override - public void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { + public synchronized void log(LogLevel logLevel, LogFormat logFormat, byte[] message) { messages.add(message); } @@ -62,6 +67,40 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } + // Makes Sure Logging Contexts are thread local. + @Test + public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { + TestSink sink = new TestSink(); + LambdaContextLogger logger = new LambdaContextLogger(sink, LogLevel.INFO, LogFormat.JSON); + + String someMessagePrefix = "Some Message from "; + String reqIDPrefix = "Thread ID as request# "; + + final int nThreads = 5; + ExecutorService es = Executors.newFixedThreadPool(nThreads); + for (int i = 0; i < nThreads; i++) { + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + } + + final int nMessages = 100_000; + for (int i = 0; i < nMessages; i++) { + es.submit(() -> logger.log(someMessagePrefix + Thread.currentThread().getName())); + } + + es.shutdown(); + while (!es.isTerminated()) { + ; + } + + assertEquals(nMessages, sink.getMessages().size()); + for (byte[] message : sink.getMessages()) { + JSONObject parsedLog = new JSONObject(new String(message, StandardCharsets.UTF_8)); + String parsedMessage = parsedLog.getString("message"); + String parsedReqID = parsedLog.getString("AWSRequestId"); + assertEquals(parsedMessage.substring(someMessagePrefix.length()), parsedReqID.substring(reqIDPrefix.length())); + } + } + @Test public void testLoggingNullValuesWithoutLogLevelInJSON() { TestSink sink = new TestSink(); From 1fde59f45783a8bb150b4bd10fe3a4cccb563675 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:24:33 +0100 Subject: [PATCH 072/173] Modified RIC Behavior for Retries and Failure Handling (#84) * Modified RIC Behavior for Retries and Failure Handling + Refactoring --- .../lambda/runtime/api/client/AWSLambda.java | 97 ++++++----- .../ReservedRuntimeEnvironmentVariables.java | 11 +- .../lambda/runtime/api/client/UserFault.java | 2 +- .../runtimeapi/LambdaRuntimeApiClient.java | 6 + .../LambdaRuntimeApiClientImpl.java | 70 ++++++++ ...timeClientMaxRetriesExceededException.java | 15 ++ .../api/client/util/ConcurrencyConfig.java | 30 ++-- .../runtime/api/client/AWSLambdaTest.java | 150 ++++++++++++++++-- .../logging/AbstractLambdaLoggerTest.java | 7 +- .../LambdaRuntimeApiClientImplTest.java | 83 +++++++++- .../client/util/ConcurrencyConfigTest.java | 29 ++-- 11 files changed, 399 insertions(+), 101 deletions(-) create mode 100644 aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index fdd090077..9c6dd78b1 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -15,6 +15,7 @@ import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaError; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClient; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.RapidErrorType; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter; @@ -235,7 +236,7 @@ private static LambdaContextLogger initLogger() { private static void startRuntimeLoopWithExecutor(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, ExecutorService executorService, LambdaRuntimeApiClient runtimeClient) { executorService.submit(() -> { try { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, false); } catch (Exception e) { lambdaLogger.log(String.format("Runtime Loop on Thread ID: %s Failed.\n%s", Thread.currentThread().getName(), UserFault.trace(e)), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); } @@ -259,49 +260,73 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } } else { - startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient); + startRuntimeLoop(lambdaRequestHandler, lambdaLogger, runtimeClient, true); } } - private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient) throws Exception { + private static LambdaError createLambdaErrorFromThrowableOrUserFault(Throwable t) { + if (t instanceof UserFault) { + return new LambdaError( + LambdaErrorConverter.fromUserFault((UserFault) t), + RapidErrorType.BadFunctionCode); + } else { + return new LambdaError( + LambdaErrorConverter.fromThrowable(t), + XRayErrorCauseConverter.fromThrowable(t), + RapidErrorType.UserException); + } + } + + private static void setEnvVarForXrayTraceId(InvocationRequest request) { + if (request.getXrayTraceId() != null) { + System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); + } else { + System.clearProperty(LAMBDA_TRACE_HEADER_PROP); + } + } + + private static void reportNonLoopTerminatingException(LambdaContextLogger lambdaLogger, Throwable t) { + lambdaLogger.log( + String.format( + "Runtime Loop on Thread ID: %s Faced and Exception. This exception will not stop the runtime loop.\nException:\n%s", + Thread.currentThread().getName(), UserFault.trace(t)), + lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + /* + * In multiconcurrent mode (exitLoopOnErrors = false), The Runtime Loop will not exit unless LambdaRuntimeClientMaxRetriesExceededException is thrown when calling nextInvocationWithExponentialBackoff. + * In normal/sequential mode (exitLoopOnErrors = true), The Runtime Loop will exit if nextInvocation call fails, when UserFault is fatal, or an Error of type VirtualMachineError or IOError is thrown. + */ + private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, LambdaContextLogger lambdaLogger, LambdaRuntimeApiClient runtimeClient, boolean exitLoopOnErrors) throws Exception { boolean shouldExit = false; while (!shouldExit) { - UserFault userFault = null; - InvocationRequest request = runtimeClient.nextInvocation(); - if (request.getXrayTraceId() != null) { - System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId()); - } else { - System.clearProperty(LAMBDA_TRACE_HEADER_PROP); - } - - ByteArrayOutputStream payload; try { - payload = lambdaRequestHandler.call(request); - runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); - // clear interrupted flag in case if it was set by user's code - Thread.interrupted(); - } catch (UserFault f) { - shouldExit = f.fatal; - userFault = f; - UserFault.filterStackTrace(f); - LambdaError error = new LambdaError( - LambdaErrorConverter.fromUserFault(f), - RapidErrorType.BadFunctionCode); - runtimeClient.reportInvocationError(request.getId(), error); + UserFault userFault = null; + InvocationRequest request = exitLoopOnErrors ? runtimeClient.nextInvocation() : runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger); + setEnvVarForXrayTraceId(request); + + try { + ByteArrayOutputStream payload = lambdaRequestHandler.call(request); + runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); + // clear interrupted flag in case if it was set by user's code + Thread.interrupted(); + } catch (Throwable t) { + UserFault.filterStackTrace(t); + userFault = UserFault.makeUserFault(t); + shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal); + LambdaError error = createLambdaErrorFromThrowableOrUserFault(t); + runtimeClient.reportInvocationError(request.getId(), error); + } finally { + if (userFault != null) { + lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + } } catch (Throwable t) { - shouldExit = t instanceof VirtualMachineError || t instanceof IOError; - UserFault.filterStackTrace(t); - userFault = UserFault.makeUserFault(t); - - LambdaError error = new LambdaError( - LambdaErrorConverter.fromThrowable(t), - XRayErrorCauseConverter.fromThrowable(t), - RapidErrorType.UserException); - runtimeClient.reportInvocationError(request.getId(), error); - } finally { - if (userFault != null) { - lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + if (exitLoopOnErrors || t instanceof LambdaRuntimeClientMaxRetriesExceededException) { + throw t; } + + reportNonLoopTerminatingException(lambdaLogger, t); } } } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 2d32d4048..1de3dca99 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -107,16 +107,9 @@ public interface ReservedRuntimeEnvironmentVariables { */ String TZ = "TZ"; - /** - * Boolean determining whether the RIC should run multi-concurrent runtime loops. Default value is "false". - * In case it is set to "true", AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY can be used to set the required number of concurrent runtime loops. - */ - String AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC = "AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC"; - /* * Used to set the required number of concurrent runtime loops, - * Only used if AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC is set to "true", - * If AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. */ - String AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY = "AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY"; + String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java index c7c5c9ddf..7d8a50347 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/UserFault.java @@ -38,7 +38,7 @@ public UserFault(String msg, String exception, String trace, Boolean fatal) { * No more user code should run after a fault. */ public static UserFault makeUserFault(Throwable t) { - return makeUserFault(t, false); + return t instanceof UserFault ? (UserFault) t : makeUserFault(t, false); } public static UserFault makeUserFault(Throwable t, boolean fatal) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java index e2ae0969a..a62aeb9b8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java @@ -4,6 +4,7 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import java.io.IOException; @@ -24,6 +25,11 @@ public interface LambdaRuntimeApiClient { */ InvocationRequest nextInvocation() throws IOException; + /** + * Get next invocation with exponential backoff + */ + InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception; + /** * Report invocation success * @param requestId request id diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java index 65024b98e..caca69aa7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java @@ -4,7 +4,11 @@ */ package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; +import com.amazonaws.services.lambda.runtime.api.client.UserFault; +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; +import com.amazonaws.services.lambda.runtime.logging.LogFormat; +import com.amazonaws.services.lambda.runtime.logging.LogLevel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,6 +18,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; @@ -30,6 +36,11 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient { private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"; // 1MiB private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024; + + // ~32 Seconds Max Backoff. + private static final long MAX_BACKOFF_PERIOD_MS = 1024 * 32; + private static final long INITIAL_BACKOFF_PERIOD_MS = 100; + private static final int MAX_NUMBER_OF_RETRIALS = 5; private final String baseUrl; private final String invocationEndpoint; @@ -52,6 +63,65 @@ public InvocationRequest nextInvocation() { return NativeClient.next(); } + /* + * Retry immediately then retry with exponential backoff. + */ + public static T getSupplierResultWithExponentialBackoff(LambdaContextLogger lambdaLogger, long initialDelayMS, long maxBackoffPeriodMS, int maxNumOfAttempts, Supplier supplier, Function exceptionMessageComposer, Exception maxRetriesException) throws Exception { + long delayMS = initialDelayMS; + for (int attempts = 0; attempts < maxNumOfAttempts; attempts++) { + boolean isFirstAttempt = attempts == 0; + boolean isLastAttempt = (attempts + 1) == maxNumOfAttempts; + + // Try and log whichever exceptions happened + try { + return supplier.get(); + } catch (Exception e) { + String logMessage = exceptionMessageComposer.apply(e); + if (!isLastAttempt) { + logMessage += String.format("\nRetrying%s", isFirstAttempt ? "." : String.format(" in %d ms.", delayMS)); + } + + lambdaLogger.log(logMessage, lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + } + + // throw if ran out of attempts. + if (isLastAttempt) { + throw maxRetriesException; + } + + // update the delay duration. + if (!isFirstAttempt) { + try { + Thread.sleep(delayMS); + delayMS = Math.min(delayMS * 2, maxBackoffPeriodMS); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } + } + + // Should Not be reached. + throw new IllegalStateException(); + } + + @Override + public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogger lambdaLogger) throws Exception { + Supplier nextInvocationSupplier = () -> nextInvocation(); + Function exceptionMessageComposer = (e) -> { + return String.format("Runtime Loop on Thread ID: %s Failed to fetch next invocation.\n%s", Thread.currentThread().getName(), UserFault.trace(e)); + }; + + return getSupplierResultWithExponentialBackoff( + lambdaLogger, + INITIAL_BACKOFF_PERIOD_MS, + MAX_BACKOFF_PERIOD_MS, + MAX_NUMBER_OF_RETRIALS, + nextInvocationSupplier, + exceptionMessageComposer, + new LambdaRuntimeClientMaxRetriesExceededException("Get Next Invocation") + ); + } + @Override public void reportInvocationSuccess(String requestId, byte[] response) { NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java new file mode 100644 index 000000000..467afa25c --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeClientMaxRetriesExceededException.java @@ -0,0 +1,15 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +package com.amazonaws.services.lambda.runtime.api.client.runtimeapi; + +public class LambdaRuntimeClientMaxRetriesExceededException extends LambdaRuntimeClientException { + // 429 is possible; however, that is more appropriate when a server is responding to a spamming client that it wants to rate limit. + // In Our case, however, the RIC is a client that is not able to get a response from an upstream server, so 500 is more appropriate. + public LambdaRuntimeClientMaxRetriesExceededException(String operationName) { + super("Maximum Number of retries have been exceed" + (operationName.equals(null) + ? String.format(" for the %s operation.", operationName) + : "."), 500); + } +} diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index db1f6c218..96fbd4b79 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -12,33 +12,27 @@ import com.amazonaws.services.lambda.runtime.logging.LogLevel; public class ConcurrencyConfig { - private static final int AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT = 1000; + private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; - private final boolean isConcurrencyEnabled; public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); } public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { - this.isConcurrencyEnabled = Boolean.parseBoolean(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)); - int platformThreads = this.isConcurrencyEnabled ? Runtime.getRuntime().availableProcessors() : 0; - - if (this.isConcurrencyEnabled) { - try { - int readNumOfPlatformThreads = Integer.parseUnsignedInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads > 0 && readNumOfPlatformThreads <= AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT) { - platformThreads = readNumOfPlatformThreads; - } else { - throw new IllegalArgumentException(); - } - } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to number of cores as number of platform threads.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY, AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + int readNumOfPlatformThreads = 0; + try { + readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); } + } catch (Exception e) { + String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + readNumOfPlatformThreads = 0; } - this.numberOfPlatformThreads = platformThreads; + this.numberOfPlatformThreads = readNumOfPlatformThreads; } public String getConcurrencyConfigMessage() { @@ -46,7 +40,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.isConcurrencyEnabled && this.numberOfPlatformThreads >= 2; + return this.numberOfPlatformThreads > 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java index 4412214e9..de1f9e73f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.*; import java.io.ByteArrayOutputStream; +import java.io.IOError; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -19,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl; +import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClientMaxRetriesExceededException; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.util.ConcurrencyConfig; import com.amazonaws.services.lambda.runtime.api.client.util.EnvReader; @@ -115,6 +117,8 @@ private static InvocationRequest getFakeInvocationRequest(String id) { return request; } + private static final LambdaRuntimeClientMaxRetriesExceededException fakelambdaRuntimeClientMaxRetriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Fake max retries happened"); + @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -135,7 +139,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) @@ -143,10 +147,10 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -171,20 +175,23 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); - when(runtimeClient.nextInvocation()) + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) .thenReturn(failImmediatelyRequest) .thenReturn(userFaultRequest) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) - .thenReturn(null) - .thenReturn(null) - .thenReturn(null); + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); - // One for each of failImmediatelyRequest and userFaultRequest in finally block + three from the uncaught null expections. - verify(lambdaLogger, times(5)).log(anyString(), eq(LogLevel.ERROR)); - + // One for each of failImmediatelyRequest and userFaultRequest in finally block + // Four for crashing the Four runtime loops in the outermost catch of the runtime loop after the Null responses. + // 2 + 4 = 6 + verify(lambdaLogger, times(6)).log(anyString(), eq(LogLevel.ERROR)); + // Failed invokes should be reported. verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); @@ -198,24 +205,86 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { // Hashmap total count should equal all tasks that ran * number of iterations per task assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExceededException() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(true); + when(concurrencyConfig.getNumberOfPlatformThreads()).thenReturn(1); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocationWithExponentialBackoff(lambdaLogger)) + .thenReturn(failImmediatelyRequest) + .thenReturn(userFaultRequest) + .thenReturn(virtualMachineErrorRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenThrow(fakelambdaRuntimeClientMaxRetriesExceededException) + .thenReturn(successfullInvocationRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for each of failImmediatelyRequest, userFaultRequest, and virtualMachineErrorRequest + One for the runtime loop thread crashing. + verify(lambdaLogger, times(4)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + + // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + + /* + * + * NON-CONCURRENT-MODE TESTS + * + */ @Test @Timeout(value = 1, unit = TimeUnit.MINUTES) - void testSequentialWithFailures() throws Throwable { + void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); - InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); - InvocationRequest userFaultRequest = mock(InvocationRequest.class); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. final String UserFaultID = "Injected Fault Request ID"; when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); when(runtimeClient.nextInvocation()) .thenReturn(successfullInvocationRequest) .thenReturn(successfullInvocationRequest) .thenReturn(failImmediatelyRequest) - .thenReturn(userFaultRequest); + .thenReturn(userFaultRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(virtualMachineErrorRequest); AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); @@ -226,8 +295,55 @@ void testSequentialWithFailures() throws Throwable { verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); - // Success Reports Must Equal number of tasks that ran successfully. + // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and userFaultRequest. + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any()); + + // Hashmap keys should equal one as it is not multithreaded. + assertEquals(1, SampleHandler.hashMap.size()); + + // Hashmap total count should equal all tasks that ran * number of iterations per task + assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest successfullInvocationRequest = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + InvocationRequest failImmediatelyRequest = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); // recoverable error in all modes. + + InvocationRequest userFaultRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String UserFaultID = "Injected Fault Request ID"; + when(userFaultRequest.getId()).thenThrow(UserFault.makeUserFault(new Exception("OH NO"), true)).thenReturn(UserFaultID); + + InvocationRequest virtualMachineErrorRequest = mock(InvocationRequest.class); // unrecoverable in sequential but recoverable in multiconcurrent mode. + final String IOErrorID = "ioerr1"; + when(virtualMachineErrorRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn(IOErrorID); + + when(runtimeClient.nextInvocation()) + .thenReturn(successfullInvocationRequest) + .thenReturn(successfullInvocationRequest) + .thenReturn(failImmediatelyRequest) + .thenReturn(virtualMachineErrorRequest) + // these two should not be even feltched since userFaultRequest is not recoverable. + .thenReturn(successfullInvocationRequest) + .thenReturn(userFaultRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + // One for failImmediatelyRequest and userFaultRequest in finally block. + verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); + + // Failed invokes should be reported. + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + + // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and virtualMachineErrorRequest. verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any()); // Hashmap keys should equal one as it is not multithreaded. assertEquals(1, SampleHandler.hashMap.size()); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index 151e702af..f97602e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -67,7 +67,12 @@ public void testLoggingNullValuesWithoutLogLevelInText() { assertEquals("null", new String(sink.getMessages().get(1))); } - // Makes Sure Logging Contexts are thread local. + /* + * Makes Sure Logging Contexts are thread local. + * We start `setLambdaContext` operations using the **single** shared `logger` object on a fixed thread pool, differentiating them with thread IDs. + * We then start concurrent `log` operations which are scheduled using that fixed pool. + * It is then verified that a given log operation, which logs the thread ID it is running on, used a context that had the same thread ID. + */ @Test public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { TestSink sink = new TestSink(); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java index 473e2aef3..710c1565e 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java @@ -14,6 +14,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest; import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.StackElement; @@ -22,8 +24,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import okhttp3.HttpUrl; import static java.net.HttpURLConnection.HTTP_ACCEPTED; import static java.net.HttpURLConnection.HTTP_OK; @@ -36,6 +48,14 @@ @DisabledOnOs(OS.MAC) public class LambdaRuntimeApiClientImplTest { + @SuppressWarnings("rawtypes") + private final Supplier mockSupplier = mock(Supplier.class); + @SuppressWarnings("rawtypes") + private final Function mockExceptionMessageComposer = mock(Function.class); + private final LambdaContextLogger mockLambdaContextLogger = mock(LambdaContextLogger.class); + private final LambdaRuntimeClientMaxRetriesExceededException retriesExceededException = new LambdaRuntimeClientMaxRetriesExceededException("Testing Invocations"); + final String fakeExceptionMessage = "Something bad"; + MockWebServer mockWebServer; LambdaRuntimeApiClientImpl lambdaRuntimeApiClientImpl; @@ -43,7 +63,7 @@ public class LambdaRuntimeApiClientImplTest { ErrorRequest errorRequest = new ErrorRequest("testErrorMessage", "testErrorType", errorStackStrace); String requestId = "1234"; - + @BeforeEach void setUp() { mockWebServer = new MockWebServer(); @@ -51,6 +71,67 @@ void setUp() { lambdaRuntimeApiClientImpl = new LambdaRuntimeApiClientImpl(hostnamePort); } + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffAllFailing() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 10 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 20 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffTwoFailingThenSuccess() throws Exception { + InvocationRequest fakeRequest = new InvocationRequest(); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + when(mockSupplier.get()) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))) + .thenReturn(fakeRequest); + + InvocationRequest invocationRequest = (InvocationRequest) LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 5, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + + assertEquals(fakeRequest, invocationRequest); + verify(mockSupplier, times(3)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 5 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(anyString(), any()); + } + + @SuppressWarnings("unchecked") + @Test + public void testgetSupplierResultWithExponentialBackoffDoesntGoAboveMax() throws Exception { + + when(mockSupplier.get()).thenThrow(new RuntimeException(new Exception(fakeExceptionMessage))); + + when(mockExceptionMessageComposer.apply(any())).thenReturn(fakeExceptionMessage); + + try { + LambdaRuntimeApiClientImpl.getSupplierResultWithExponentialBackoff(mockLambdaContextLogger, 100, 200, 5, mockSupplier, mockExceptionMessageComposer, retriesExceededException); + } catch (LambdaRuntimeClientMaxRetriesExceededException e) { } + + verify(mockSupplier, times(5)).get(); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage + "\nRetrying in 100 ms."), any()); + verify(mockLambdaContextLogger, times(2)).log(eq(fakeExceptionMessage + "\nRetrying in 200 ms."), any()); + verify(mockLambdaContextLogger).log(eq(fakeExceptionMessage), any()); + verify(mockLambdaContextLogger, times(5)).log(anyString(), any()); + } + @Test public void reportInitErrorTest() { try { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index bbb43cd14..ef1d832ec 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -30,18 +30,16 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn(null); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(0, config.getNumberOfPlatformThreads()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testValidPlatformThreadsConfig() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(4, config.getNumberOfPlatformThreads()); @@ -51,31 +49,28 @@ void testValidPlatformThreadsConfig() { @Test void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(Runtime.getRuntime().availableProcessors(), config.getNumberOfPlatformThreads()); + assertEquals(0, config.getNumberOfPlatformThreads()); verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(Runtime.getRuntime().availableProcessors() >= 2, config.isMultiConcurrent()); + assertEquals(false, config.isMultiConcurrent()); } @Test void testGetConcurrencyConfigMessage() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("true"); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_MAX_CONCURRENCY)).thenReturn("4"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; @@ -85,8 +80,6 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_ENABLE_MULTICONCURRENT_RIC)).thenReturn("false"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); From 93221da3c6d2c74d7b69f91a12a99f7eaabc1360 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:52:16 +0100 Subject: [PATCH 073/173] Merge from public repo (#88) Merge from Public to Private Repo --- .github/dependabot.yml | 1 - README.md | 4 ++-- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ---- aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ---------- aws-lambda-java-events/README.md | 2 +- aws-lambda-java-events/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- aws-lambda-java-tests/pom.xml | 2 +- .../fastJson/HelloWorldFunction/pom.xml | 2 +- .../gson/HelloWorldFunction/pom.xml | 2 +- .../moshi/HelloWorldFunction/pom.xml | 2 +- .../request-stream-handler/HelloWorldFunction/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 2 +- 17 files changed, 23 insertions(+), 30 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34d287eec..88f18ea29 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/aws-lambda-java-runtime-interface" schedule: interval: "weekly" - - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/README.md b/README.md index 7f4970949..b5153a87f 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ public class SqsHandler implements RequestHandler { com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ``` @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 ``` diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index aebc8ecd9..527e7dd46 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,7 +1,3 @@ -### September 3, 2025 -`1.4.0` -- Getter support for x-ray trace ID through the Context object - ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..77245c9be 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index ed9311a11..152765df1 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,14 +109,4 @@ public interface Context { default String getTenantId() { return null; } - - /** - * - * Returns the X-Ray trace ID associated with the request. - * - * @return null by default - */ - default String getXrayTraceId() { - return null; - } } diff --git a/aws-lambda-java-events/README.md b/aws-lambda-java-events/README.md index 87c61f345..43c25d76a 100644 --- a/aws-lambda-java-events/README.md +++ b/aws-lambda-java-events/README.md @@ -74,7 +74,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 ... diff --git a/aws-lambda-java-events/RELEASE.CHANGELOG.md b/aws-lambda-java-events/RELEASE.CHANGELOG.md index 6c1769751..a4bcd10a0 100644 --- a/aws-lambda-java-events/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-events/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 17, 2025 +`3.16.0`: +- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548)) + ### January 31, 2025 `3.15.0`: - Fix `CognitoUserPoolPreTokenGenerationEventV2` model ([#519](https://github.com/aws/aws-lambda-java-libs/pull/519)) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index f1364e7ab..8799966be 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 368ab710a..67a5972d6 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 6a781b270..ac073ae85 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 21, 2025 +`2.7.0` +- Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) + ### August 7, 2024 `2.6.0` - Runtime API client improvements: use Lambda-Runtime-Function-Error-Type for reporting errors in format "Runtime." diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index fde515fda..866520092 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.7.0 jar AWS Lambda Java Runtime Interface Client @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.3.0 com.amazonaws diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 9e3419c69..6589ca742 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 org.junit.jupiter diff --git a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml index 7325c72a0..7d3c44246 100644 --- a/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/fastJson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml index dd3b8e9c5..fd4271824 100644 --- a/samples/custom-serialization/gson/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/gson/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml index f23214976..2773ef1f1 100644 --- a/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/moshi/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 diff --git a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml index 68e7e81e9..f6ca21bf7 100644 --- a/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml +++ b/samples/custom-serialization/request-stream-handler/HelloWorldFunction/pom.xml @@ -20,7 +20,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 com.google.code.gson diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3d03205d3..56c959244 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -46,7 +46,7 @@ com.amazonaws aws-lambda-java-events - 3.15.0 + 3.16.0 From d7e2a6d188528ec22ade0e2ae8f6cd45fcdb8de5 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:49:55 +0100 Subject: [PATCH 074/173] Version Bump to 2.8 (#89) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b5153a87f..4b22852c1 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 67a5972d6..512e66fe0 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ac073ae85..a030d288b 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.0` +- Refactoring + ### May 21, 2025 `2.7.0` - Adding support for multi tenancy ([#540](https://github.com/aws/aws-lambda-java-libs/pull/540)) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 866520092..a29be4279 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.0 jar AWS Lambda Java Runtime Interface Client From 2cf626b817042b65016d4c8532259d8c5a819ff2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:39:45 +0100 Subject: [PATCH 075/173] Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set (#90) * Only Log Concurrency Warning Message when AWS_LAMBDA_MAX_CONCURRENCY is set * Var name change --------- Co-authored-by: Mohammed Ehab --- .../api/client/util/ConcurrencyConfig.java | 19 ++++++++++++++----- .../client/util/ConcurrencyConfigTest.java | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index 96fbd4b79..d98ed71c8 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -22,14 +22,23 @@ public ConcurrencyConfig(LambdaContextLogger logger) { public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { int readNumOfPlatformThreads = 0; try { - readNumOfPlatformThreads = Integer.parseInt(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { - throw new IllegalArgumentException(); + String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + + // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. + if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); + if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + throw new IllegalArgumentException(); + } } } catch (Exception e) { - String message = String.format("User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); readNumOfPlatformThreads = 0; + String message = String.format( + "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, + AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, + UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); } this.numberOfPlatformThreads = readNumOfPlatformThreads; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index ef1d832ec..248625e37 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -29,10 +29,10 @@ class ConcurrencyConfigTest { @Test void testDefaultConfiguration() { - when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From e35157b9751492ea65a80716144205b248a500de Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:05:09 +0100 Subject: [PATCH 076/173] Version Bump RIC to 2.8.1 (#91) * Version Bump RIC to 2.8.1 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 2 +- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b22852c1..0870ec2b7 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 512e66fe0..90e2c0bb5 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index a030d288b..daa8bde90 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.1` +- Refactoring + ### June 26, 2025 `2.8.0` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a29be4279..92d9d0f40 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.0 + 2.8.1 jar AWS Lambda Java Runtime Interface Client From ddb34451478eb57042fdd332b9caa2afd7a0bdf2 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:08:43 +0100 Subject: [PATCH 077/173] Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise and Version Bump to 2.8.2 (#92) * Allow AWS_LAMBDA_MAX_CONCURRENCY to be one and crash the RIC Otherwise. * Version Bump to 2.8.2 --------- Authored-by: Mohammed Ehab --- README.md | 2 +- .../README.md | 2 +- .../RELEASE.CHANGELOG.md | 4 ++ .../pom.xml | 2 +- .../ReservedRuntimeEnvironmentVariables.java | 4 +- .../api/client/util/ConcurrencyConfig.java | 19 ++++---- .../client/util/ConcurrencyConfigTest.java | 47 ++++++++++++++----- 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0870ec2b7..70d7c7316 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index 90e2c0bb5..e3c68fe8a 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index daa8bde90..ff3d8bec5 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### June 26, 2025 +`2.8.2` +- Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. + ### June 26, 2025 `2.8.1` - Refactoring diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 92d9d0f40..59f33145e 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.1 + 2.8.2 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java index 1de3dca99..9fdec6b9f 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/ReservedRuntimeEnvironmentVariables.java @@ -108,8 +108,8 @@ public interface ReservedRuntimeEnvironmentVariables { String TZ = "TZ"; /* - * Used to set the required number of concurrent runtime loops, - * If AWS_LAMBDA_MAX_CONCURRENCY is not set, the default number of concurrent runtime loops is the number of cores. + * If set to a string parsable as an integer > 0, It enables multiconcurrency mode. + * Otherwise, if it is set to an invalid value, it will crash the whole RIC process. */ String AWS_LAMBDA_MAX_CONCURRENCY = "AWS_LAMBDA_MAX_CONCURRENCY"; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java index d98ed71c8..7108c7801 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfig.java @@ -14,6 +14,8 @@ public class ConcurrencyConfig { private static final int AWS_LAMBDA_MAX_CONCURRENCY_LIMIT = 1000; private final int numberOfPlatformThreads; + private final String INVALID_CONFIG_MESSAGE_PREFIX = String.format("User configured %s is invalid. Please make sure it is a positive number more than zero and less than or equal %d", + ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, AWS_LAMBDA_MAX_CONCURRENCY_LIMIT); public ConcurrencyConfig(LambdaContextLogger logger) { this(logger, new EnvReader()); @@ -24,21 +26,16 @@ public ConcurrencyConfig(LambdaContextLogger logger, EnvReader envReader) { try { String readLambdaMaxConcurrencyEnvVar = envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); - // Log only if env var is actually set to an invalid value. Otherwise default to no concurrency silently. - if (!(readLambdaMaxConcurrencyEnvVar == null || readLambdaMaxConcurrencyEnvVar.isEmpty())) { + if (readLambdaMaxConcurrencyEnvVar != null) { readNumOfPlatformThreads = Integer.parseInt(readLambdaMaxConcurrencyEnvVar); - if (readNumOfPlatformThreads <= 0 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { + if (readNumOfPlatformThreads < 1 || readNumOfPlatformThreads > AWS_LAMBDA_MAX_CONCURRENCY_LIMIT) { throw new IllegalArgumentException(); } } } catch (Exception e) { - readNumOfPlatformThreads = 0; - String message = String.format( - "User configured %s is not valid. Please make sure it is a positive number more than zero and less than or equal %d\n%s\nDefaulting to no concurrency.", - ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY, - AWS_LAMBDA_MAX_CONCURRENCY_LIMIT, - UserFault.trace(e)); - logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.WARN : LogLevel.UNDEFINED); + String message = String.format("%s\n%s", INVALID_CONFIG_MESSAGE_PREFIX, UserFault.trace(e)); + logger.log(message, logger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); + throw e; } this.numberOfPlatformThreads = readNumOfPlatformThreads; @@ -49,7 +46,7 @@ public String getConcurrencyConfigMessage() { } public boolean isMultiConcurrent() { - return this.numberOfPlatformThreads > 1; + return this.numberOfPlatformThreads >= 1; } public int getNumberOfPlatformThreads() { diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java index 248625e37..096d52bb0 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/util/ConcurrencyConfigTest.java @@ -14,8 +14,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -27,6 +28,8 @@ class ConcurrencyConfigTest { @Mock private EnvReader envReader; + private static final String exitingRuntimeString = String.format("User configured %s is invalid.", ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY); + @Test void testDefaultConfiguration() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn(null); @@ -37,35 +40,51 @@ void testDefaultConfiguration() { assertEquals(false, config.isMultiConcurrent()); } + @Test + void testBelowMinPlatformThreadsLimit() { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("0"); + + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); + } + + @Test + void testMinValidPlatformThreadsConfig() { + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1"); + + ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); + assertEquals(1, config.getNumberOfPlatformThreads()); + assertEquals(true, config.isMultiConcurrent()); + } + @Test void testValidPlatformThreadsConfig() { when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("4"); ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(4, config.getNumberOfPlatformThreads()); assertEquals(true, config.isMultiConcurrent()); } @Test - void testInvalidPlatformThreadsConfig() { + void testExceedingPlatformThreadsLimit() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(IllegalArgumentException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test - void testExceedingPlatformThreadsLimit() { + void testInvalidPlatformThreadsConfig() { when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); - when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("1001"); + when(envReader.getEnv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_MAX_CONCURRENCY)).thenReturn("invalid"); - ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); - assertEquals(0, config.getNumberOfPlatformThreads()); - verify(lambdaLogger).log(anyString(), eq(LogLevel.WARN)); - assertEquals(false, config.isMultiConcurrent()); + assertThrows(NumberFormatException.class, () -> new ConcurrencyConfig(lambdaLogger, envReader)); + verify(lambdaLogger).log(contains(exitingRuntimeString), eq(LogLevel.ERROR)); } @Test @@ -74,6 +93,7 @@ void testGetConcurrencyConfigMessage() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); String expectedMessage = "Starting 4 concurrent function handler threads."; + verifyNoInteractions(lambdaLogger); assertEquals(expectedMessage, config.getConcurrencyConfigMessage()); assertEquals(true, config.isMultiConcurrent()); } @@ -81,6 +101,7 @@ void testGetConcurrencyConfigMessage() { @Test void testGetConcurrencyConfigWithNoConcurrency() { ConcurrencyConfig config = new ConcurrencyConfig(lambdaLogger, envReader); + verifyNoInteractions(lambdaLogger); assertEquals(0, config.getNumberOfPlatformThreads()); assertEquals(false, config.isMultiConcurrent()); } From 8fe3580bbb419c3adfc6de99abe5fd27fdbf8720 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:08:43 +0100 Subject: [PATCH 078/173] =?UTF-8?q?Version=20bump:=20serialization=201.1.5?= =?UTF-8?q?=E2=86=921.1.6,=20events=203.16.0=E2=86=923.16.1,=20update=20de?= =?UTF-8?q?pendencies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump aws-lambda-java-serialization from 1.1.5 to 1.1.6 - Bump aws-lambda-java-events from 3.16.0 to 3.16.1 - Update aws-lambda-java-tests dependencies to use new versions - Update aws-lambda-java-runtime-interface-client serialization dependency 1.1.2→1.1.6 - Update aws-lambda-java-events-sdk-transformer events dependency 3.11.2→3.16.1 --- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 8799966be..925273e9b 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 jar AWS Lambda Java Events Library diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 6589ca742..da07133c1 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -45,7 +45,7 @@ com.amazonaws aws-lambda-java-events - 3.16.0 + 3.16.1 org.junit.jupiter From 428d1255901ba2280ba533bcc90b98f6c46611a8 Mon Sep 17 00:00:00 2001 From: Astraea Quinn Sinclair Date: Tue, 15 Jul 2025 09:26:46 +0100 Subject: [PATCH 079/173] Fix runtime interface client workflows to use local serialization dependency Add local build step for aws-lambda-java-serialization before building runtime interface client. ## Why This Fix is Needed The runtime interface client depends on aws-lambda-java-serialization version 1.1.6, but this version doesn't exist in Maven Central yet. By building and installing the serialization package locally first, we ensure: 1. The correct version (1.1.6) is available in the local Maven repository 2. The runtime interface client build won't fail looking for a non-existent version on Maven Central 3. The workflow tests the actual code changes together ## Changes Made - runtime-interface-client_merge_to_main.yml: Added local serialization build step - runtime-interface-client_pr.yml: Added local serialization build step to both smoke-test and build jobs This ensures CI/CD pipeline works correctly with the new dependency versions. --- .../workflows/runtime-interface-client_merge_to_main.yml | 4 ++++ .github/workflows/runtime-interface-client_pr.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index e07b191e1..88f8afde2 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -47,6 +47,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index cbc5b5ab1..d1821d2ce 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -31,6 +31,10 @@ jobs: working-directory: ./aws-lambda-java-serialization run: mvn clean install -DskipTests + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client run: make pr @@ -59,6 +63,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + - name: Build and install serialization dependency locally + working-directory: ./aws-lambda-java-serialization + run: mvn clean install -DskipTests + - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client run: make build From 477c0805ed9247d9c6f7abc567d68f6693ce7826 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:12:48 +0100 Subject: [PATCH 080/173] Update ric pom.xml --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 59f33145e..7a0d0264a 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.3 jar AWS Lambda Java Runtime Interface Client From 44cd05e502c4717400a398a4a6b9276044588b6c Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:41:48 +0100 Subject: [PATCH 081/173] Update runtime-interface-client_merge_to_main.yml --- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 88f8afde2..94294501c 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -49,7 +49,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From ba4ef1c0862e5997ac9764bf07a7e108d11bece3 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:02:05 +0100 Subject: [PATCH 082/173] Update runtime-interface-client_pr.yml --- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index d1821d2ce..b5631d2b4 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -33,7 +33,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Runtime Interface Client smoke tests - Run 'pr' target working-directory: ./aws-lambda-java-runtime-interface-client @@ -65,7 +65,7 @@ jobs: - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests + run: mvn clean install - name: Test Runtime Interface Client xplatform build - Run 'build' target working-directory: ./aws-lambda-java-runtime-interface-client From 3ac07d05773a299bfc10faa0dd0ce3e854f07af4 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:06:33 +0100 Subject: [PATCH 083/173] Ensure EventHandlerLoader Thread Safety. (#95) * Make handler response buffers thread safe. * Add multiconcurrency tests * ThreadLocal instead of Allocating new buffers every invoke. * Thread local log4jContextPutMethod * Fix indentations * Add CountDownLatch to ensure all calls are done simultaneously. --------- Co-authored-by: Mohammed Ehab --- .../api/client/EventHandlerLoader.java | 37 ++++----- .../api/client/EventHandlerLoaderTest.java | 79 ++++++++++++++++++- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index af146fd93..afb3e3df7 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -57,10 +57,10 @@ private enum Platform { UNKNOWN } - private static volatile PojoSerializer contextSerializer; - private static volatile PojoSerializer cognitoSerializer; + private static volatile ThreadLocal> contextSerializer = new ThreadLocal<>(); + private static volatile ThreadLocal> cognitoSerializer = new ThreadLocal<>(); - private static final EnumMap>> typeCache = new EnumMap<>(Platform.class); + private static final ThreadLocal>>> typeCache = ThreadLocal.withInitial(() -> new EnumMap<>(Platform.class)); private static final Comparator methodPriority = new Comparator() { public int compare(Method lhs, Method rhs) { @@ -127,10 +127,11 @@ private static PojoSerializer getSerializer(Platform platform, Type type } private static PojoSerializer getSerializerCached(Platform platform, Type type) { - Map> cache = typeCache.get(platform); + EnumMap>> threadTypeCache = typeCache.get(); + Map> cache = threadTypeCache.get(platform); if (cache == null) { cache = new HashMap<>(); - typeCache.put(platform, cache); + threadTypeCache.put(platform, cache); } PojoSerializer serializer = cache.get(type); @@ -143,17 +144,17 @@ private static PojoSerializer getSerializerCached(Platform platform, Typ } private static PojoSerializer getContextSerializer() { - if (contextSerializer == null) { - contextSerializer = GsonFactory.getInstance().getSerializer(LambdaClientContext.class); + if (contextSerializer.get() == null) { + contextSerializer.set(GsonFactory.getInstance().getSerializer(LambdaClientContext.class)); } - return contextSerializer; + return contextSerializer.get(); } private static PojoSerializer getCognitoSerializer() { - if (cognitoSerializer == null) { - cognitoSerializer = GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class); + if (cognitoSerializer.get() == null) { + cognitoSerializer.set(GsonFactory.getInstance().getSerializer(LambdaCognitoIdentity.class)); } - return cognitoSerializer; + return cognitoSerializer.get(); } @@ -527,15 +528,14 @@ private static LambdaRequestHandler wrapPojoHandler(RequestHandler instance, Typ private static LambdaRequestHandler wrapRequestStreamHandler(final RequestStreamHandler handler) { return new LambdaRequestHandler() { - private final ByteArrayOutputStream output = new ByteArrayOutputStream(1024); - private Functions.V2 log4jContextPutMethod = null; + private final ThreadLocal outputBuffers = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(1024)); + private ThreadLocal> log4jContextPutMethod = new ThreadLocal<>(); - private void safeAddRequestIdToLog4j(String log4jContextClassName, - InvocationRequest request, Class contextMapValueClass) { + private void safeAddRequestIdToLog4j(String log4jContextClassName, InvocationRequest request, Class contextMapValueClass) { try { Class log4jContextClass = ReflectUtil.loadClass(AWSLambda.getCustomerClassLoader(), log4jContextClassName); - log4jContextPutMethod = ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass); - log4jContextPutMethod.call("AWSRequestId", request.getId()); + log4jContextPutMethod.set(ReflectUtil.loadStaticV2(log4jContextClass, "put", false, String.class, contextMapValueClass)); + log4jContextPutMethod.get().call("AWSRequestId", request.getId()); } catch (Exception e) { // nothing to do here } @@ -558,6 +558,7 @@ private void safeAddContextToLambdaLogger(LambdaContext context) { } public ByteArrayOutputStream call(InvocationRequest request) throws Error, Exception { + ByteArrayOutputStream output = outputBuffers.get(); output.reset(); LambdaCognitoIdentity cognitoIdentity = null; @@ -591,7 +592,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep safeAddRequestIdToLog4j("org.apache.log4j.MDC", request, Object.class); safeAddRequestIdToLog4j("org.apache.logging.log4j.ThreadContext", request, String.class); // if put method not assigned in either call to safeAddRequestIdtoLog4j then log4jContextPutMethod = null - if (log4jContextPutMethod == null) { + if (log4jContextPutMethod.get() == null) { System.err.println("Customer using log4j appender but unable to load either " + "org.apache.log4j.MDC or org.apache.logging.log4j.ThreadContext. " + "Customer cannot see RequestId in log4j log lines."); diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java index 76e6f0249..aae2f1afe 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoaderTest.java @@ -4,8 +4,16 @@ import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class EventHandlerLoaderTest { @@ -37,7 +45,6 @@ void PojoHandlerTest_oneParamEvent() throws Exception { assertSuccessfulInvocation(lambdaRequestHandler); } - @Test void PojoHandlerTest_oneParamContext() throws Exception { String handler = "test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"; @@ -74,4 +81,72 @@ private static InvocationRequest getTestInvocationRequest() { invocationRequest.setXrayTraceId("traceId"); return invocationRequest; } -} \ No newline at end of file + + // Multithreaded test methods + + @Test + void RequestHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestHandlerImpl"); + } + + @Test + void RequestStreamHandlerTest_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.RequestStreamHandlerImpl"); + } + + @Test + void PojoHandlerTest_noParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::noParamsHandler"); + } + + @Test + void PojoHandlerTest_oneParamEvent_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_event"); + } + + @Test + void PojoHandlerTest_oneParamContext_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::oneParamHandler_context"); + } + + @Test + void PojoHandlerTest_twoParams_Multithreaded() throws Exception { + testHandlerConcurrency("test.lambda.handlers.POJOHanlderImpl::twoParamsHandler"); + } + + private void testHandlerConcurrency(String handlerName) throws Exception { + // Create one handler instance + LambdaRequestHandler handler = getLambdaRequestHandler(handlerName); + + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(); + CountDownLatch startLatch = new CountDownLatch(1); + + try { + for (int i = 0; i < threadCount; i++) { + futures.add(executor.submit(() -> { + try { + InvocationRequest request = getTestInvocationRequest(); + startLatch.await(); + ByteArrayOutputStream result = handler.call(request); + return result.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + // Release all threads simultaneously and Verify all invocations return the expected result + startLatch.countDown(); + + for (Future future : futures) { + String result = future.get(5, TimeUnit.SECONDS); + assertEquals("\"success\"", result); + } + } finally { + executor.shutdown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } + } +} From 1a7af454aa66eb4ea095c65de1f977c8f0dc26a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:43:27 +0100 Subject: [PATCH 084/173] Make Trace ID accessible through Context (#101) * Make Trace ID accessible through Context * Change Constructor --------- Co-authored-by: Mohammed Ehab --- .github/workflows/runtime-interface-client_pr.yml | 8 ++++---- aws-lambda-java-core/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-core/pom.xml | 2 +- .../com/amazonaws/services/lambda/runtime/Context.java | 10 ++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../lambda/runtime/api/client/EventHandlerLoader.java | 1 + .../lambda/runtime/api/client/api/LambdaContext.java | 7 +++++++ .../runtime/api/client/api/LambdaContextTest.java | 3 ++- .../api/client/logging/AbstractLambdaLoggerTest.java | 2 +- .../api/client/logging/JsonLogFormatterTest.java | 2 ++ 10 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index b5631d2b4..5819218c9 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -27,10 +27,6 @@ jobs: working-directory: ./aws-lambda-java-core run: mvn clean install - - name: Build and install serialization dependency locally - working-directory: ./aws-lambda-java-serialization - run: mvn clean install -DskipTests - - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization run: mvn clean install @@ -62,6 +58,10 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Build and install core dependency locally + working-directory: ./aws-lambda-java-core + run: mvn clean install - name: Build and install serialization dependency locally working-directory: ./aws-lambda-java-serialization diff --git a/aws-lambda-java-core/RELEASE.CHANGELOG.md b/aws-lambda-java-core/RELEASE.CHANGELOG.md index 527e7dd46..aebc8ecd9 100644 --- a/aws-lambda-java-core/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-core/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### September 3, 2025 +`1.4.0` +- Getter support for x-ray trace ID through the Context object + ### May 26, 2025 `1.3.0` - Adding support for multi tenancy ([#545](https://github.com/aws/aws-lambda-java-libs/pull/545)) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 77245c9be..cca9d0cdf 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java index 152765df1..ed9311a11 100644 --- a/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java +++ b/aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java @@ -109,4 +109,14 @@ public interface Context { default String getTenantId() { return null; } + + /** + * + * Returns the X-Ray trace ID associated with the request. + * + * @return null by default + */ + default String getXrayTraceId() { + return null; + } } diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 7a0d0264a..189743054 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -61,7 +61,7 @@ com.amazonaws aws-lambda-java-core - 1.3.0 + 1.4.0 com.amazonaws diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java index afb3e3df7..f679c217c 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/EventHandlerLoader.java @@ -583,6 +583,7 @@ public ByteArrayOutputStream call(InvocationRequest request) throws Error, Excep LambdaEnvironment.FUNCTION_VERSION, request.getInvokedFunctionArn(), request.getTenantId(), + request.getXrayTraceId(), clientContext ); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java index bd1463db6..20b77262d 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java @@ -23,6 +23,7 @@ public class LambdaContext implements Context { private final CognitoIdentity cognitoIdentity; private final ClientContext clientContext; private final String tenantId; + private final String xrayTraceId; private final LambdaLogger logger; public LambdaContext( @@ -36,6 +37,7 @@ public LambdaContext( String functionVersion, String invokedFunctionArn, String tenantId, + String xrayTraceId, ClientContext clientContext ) { this.memoryLimit = memoryLimit; @@ -49,6 +51,7 @@ public LambdaContext( this.functionVersion = functionVersion; this.invokedFunctionArn = invokedFunctionArn; this.tenantId = tenantId; + this.xrayTraceId = xrayTraceId; this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger(); } @@ -98,6 +101,10 @@ public String getTenantId() { return tenantId; } + public String getXrayTraceId() { + return xrayTraceId; + } + public LambdaLogger getLogger() { return logger; } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java index 58880be43..f7da76198 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java @@ -19,6 +19,7 @@ public class LambdaContextTest { private static final LambdaClientContext CLIENT_CONTEXT = new LambdaClientContext(); public static final int MEMORY_LIMIT = 128; public static final String TENANT_ID = "tenant-id"; + public static final String X_RAY_TRACE_ID = "x-ray-trace-id"; @Test public void getRemainingTimeInMillis() { @@ -55,6 +56,6 @@ public void getRemainingTimeInMillis_Deadline() throws InterruptedException { private LambdaContext createContextWithDeadline(long deadlineTimeInMs) { return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME, - FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, CLIENT_CONTEXT); + FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT); } } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java index f97602e37..3a5ee8d5f 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/AbstractLambdaLoggerTest.java @@ -84,7 +84,7 @@ public void testMultiConcurrentLoggingWithoutLogLevelInJSON() { final int nThreads = 5; ExecutorService es = Executors.newFixedThreadPool(nThreads); for (int i = 0; i < nThreads; i++) { - es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null))); + es.submit(() -> logger.setLambdaContext(new LambdaContext(Integer.MAX_VALUE, Long.MAX_VALUE, reqIDPrefix + Thread.currentThread().getName(), "", "", "", null, "", "", "", null, null))); } final int nMessages = 100_000; diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java index 531e9ca94..91ce9d2a3 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/logging/JsonLogFormatterTest.java @@ -30,6 +30,7 @@ void testFormattingWithLambdaContext() { null, "function-arn", null, + null, null ); assertFormatsString("test log", LogLevel.WARN, context); @@ -48,6 +49,7 @@ void testFormattingWithTenantIdInLambdaContext() { null, "function-arn", "tenant-id", + "xray-trace-id", null ); assertFormatsString("test log", LogLevel.WARN, context); From e9c084ea084f26b5aed8bcae9d466b20dc7149ba Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:46:22 +0100 Subject: [PATCH 085/173] Migrate to Deploying using Maven Central (#103) Co-authored-by: Mohammed Ehab --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 189743054..c854fabcd 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.3 + 2.8.4 jar AWS Lambda Java Runtime Interface Client From 2250e99a2ed537a2a3dde63375944ce4b70e28ff Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:26 +0100 Subject: [PATCH 086/173] Fix performance Issue By Using Blocking Calls instead of Busy Waiting (#106) Co-authored-by: Mohammed Ehab --- .../services/lambda/runtime/api/client/AWSLambda.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index 9c6dd78b1..10d595c7b 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -39,6 +39,7 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks: @@ -253,10 +254,10 @@ protected static void startRuntimeLoops(LambdaRequestHandler lambdaRequestHandle } } finally { platformThreadExecutor.shutdown(); - while (true) { - if (platformThreadExecutor.isTerminated()) { - break; - } + try { + platformThreadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } } else { From 085c9754f497b6ba3240bea3a8cef3286ba823b3 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:55 +0100 Subject: [PATCH 087/173] Log errorType and errorMessage from RAPID (#105) Co-authored-by: Mohammed Ehab --- .../src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index 3897d37db..84a84b439 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -370,7 +370,7 @@ runtime::post_outcome runtime::do_post( if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( - LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code); + LOG_TAG, "Failed to post handler success response. Http response code: %ld. %s", http_response_code, resp.get_body().c_str()); return aws::http::response_code(http_response_code); } From e61bc6342a6d731e8699f3e8e558e4664a1249a0 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:52:42 +0100 Subject: [PATCH 088/173] Version Bump RIC to 2.8.5 (#107) Co-authored-by: Mohammed Ehab --- README.md | 2 +- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- .../RELEASE.CHANGELOG.md | 13 +++++++++++++ aws-lambda-java-runtime-interface-client/pom.xml | 2 +- .../test/integration/test-handler/pom.xml | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 70d7c7316..609485625 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The purpose of this package is to allow developers to deploy their applications com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 ``` diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index e3c68fe8a..c63dcfeef 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.2 + 2.8.5 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.7.0 + 2.8.5 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index ff3d8bec5..a8c34dec8 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,16 @@ +### September 17, 2025 +`2.8.5` +- Log errorType and errorMessage from RAPID in C++ Client. +- Performance Upgrade for Multiconcurrency Mode. + +### September 9, 2025 +`2.8.4` +- Make Trace ID Accessible through Context Object. + +### July 19, 2025 +`2.8.3` +- Ensure EventHandlerLoader Thread Safety. + ### June 26, 2025 `2.8.2` - Allow AWS_LAMBDA_MAX_CONCURRENCY to be One. Crash the RIC if it is set to an un-parsable string to an integer or an out of bounds value. diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c854fabcd..b77c38e00 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.8.4 + 2.8.5 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml index 5304d44b9..35ea694ab 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml +++ b/aws-lambda-java-runtime-interface-client/test/integration/test-handler/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-runtime-interface-client - 2.6.0 + 2.8.5 From f93664f998fcf3c52787fae1dc2bceaf639c2f82 Mon Sep 17 00:00:00 2001 From: Mohammed Ehab Elsaeed <33024315+M-Elsaeed@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:13:11 +0100 Subject: [PATCH 089/173] MultiConcurrent XRAY TraceID using utils-lite (#96) Co-authored-by: Mohammed Ehab --- .../pom.xml | 38 +-- .../lambda/runtime/api/client/AWSLambda.java | 11 +- .../runtime/api/client/AWSLambdaTest.java | 230 +++++++++++++++++- 3 files changed, 258 insertions(+), 21 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index b77c38e00..0d3e7fb04 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -1,6 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client @@ -47,9 +47,9 @@ separately from the Runtime Interface Client functionality until we figure something else out. --> true - - - + + + 4.0.0 @@ -40,7 +41,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.0 + 1.2.0 com.amazonaws From af730be04a435a588c7e90d2110dfb2d605357c1 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Thu, 19 Mar 2026 17:54:59 +0000 Subject: [PATCH 108/173] Revert upgrade in Jackson serialization (#593) * chore: reverted the poms * chore: upgrading the poms * chore: update docs * chore: add targets to tests * chore: add workflow_dispatch * chore: add comment to trigger testing * chore: downgrade jackson library * chore: removing comment * chore: new version * chore: remove file --------- Co-authored-by: Davide Melfi --- .../RELEASE.CHANGELOG.md | 5 + aws-lambda-java-serialization/pom.xml | 4 +- .../events/LambdaEventSerializers.java | 524 +++++++++--------- 3 files changed, 281 insertions(+), 252 deletions(-) diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index 59ed3c8cb..4a0e8ca2a 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,3 +1,8 @@ +### March 19, 2026 +`1.3.1`: +- Revert `jackson-databind` dependency from 2.18.6 to 2.15.4 +- Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy` + ### March 11, 2026 `1.3.0`: - Update `jackson-databind` dependency from 2.15.4 to 2.18.6 diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 93c27d879..a71eb1f8d 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.0 + 1.3.1 jar AWS Lambda Java Runtime Serialization @@ -32,7 +32,7 @@ 1.8 1.8 com.amazonaws.lambda.thirdparty - 2.18.6 + 2.15.4 2.10.1 20231013 7.3.2 diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 89401a91e..3f01d99b1 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -19,7 +19,6 @@ import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil; import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule; @@ -40,279 +39,304 @@ * * Option 1 (Preferred): * 1. Add Class name to SUPPORTED_EVENTS - * 2. Add Mixin Class to com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if needed) + * 2. Add Mixin Class to + * com.amazonaws.services.lambda.runtime.serialization.events.mixins package (if + * needed) * 3. Add entries to MIXIN_MAP for event class and sub classes (if needed) - * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if needed) - * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of a mixin) + * 4. Add entries to NESTED_CLASS_MAP for event class and sub classes (if + * needed) + * 5. Add entry to NAMING_STRATEGY_MAP (if needed i.e. Could be used in place of + * a mixin) * * Option 2 (longer - for event models that do not work with Jackson or GSON): * 1. Add Class name to SUPPORTED_EVENTS - * 2. Add serializer (using org.json) to com.amazonaws.services.lambda.runtime.serialization.events.serializers + * 2. Add serializer (using org.json) to + * com.amazonaws.services.lambda.runtime.serialization.events.serializers * 3. Add class name and serializer to SERIALIZER_MAP */ public class LambdaEventSerializers { - /** - * list of supported events - */ - private static final List SUPPORTED_EVENTS = Stream.of( - "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", - "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", - "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - "com.amazonaws.services.lambda.runtime.events.CognitoEvent", - "com.amazonaws.services.lambda.runtime.events.ConfigEvent", - "com.amazonaws.services.lambda.runtime.events.ConnectEvent", - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent", - "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent", - "com.amazonaws.services.lambda.runtime.events.LexEvent", - "com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - "com.amazonaws.services.s3.event.S3EventNotification", - "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", - "com.amazonaws.services.lambda.runtime.events.S3Event", - "com.amazonaws.services.lambda.runtime.events.SNSEvent", - "com.amazonaws.services.lambda.runtime.events.SQSEvent") - .collect(Collectors.toList()); + /** + * list of supported events + */ + private static final List SUPPORTED_EVENTS = Stream.of( + "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent", + "com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent", + "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + "com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + "com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + "com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + "com.amazonaws.services.lambda.runtime.events.CognitoEvent", + "com.amazonaws.services.lambda.runtime.events.ConfigEvent", + "com.amazonaws.services.lambda.runtime.events.ConnectEvent", + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + "com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + "com.amazonaws.services.lambda.runtime.events.IoTButtonEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + "com.amazonaws.services.lambda.runtime.events.KinesisFirehoseEvent", + "com.amazonaws.services.lambda.runtime.events.LambdaDestinationEvent", + "com.amazonaws.services.lambda.runtime.events.LexEvent", + "com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + "com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + "com.amazonaws.services.s3.event.S3EventNotification", + "com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", + "com.amazonaws.services.lambda.runtime.events.S3Event", + "com.amazonaws.services.lambda.runtime.events.SNSEvent", + "com.amazonaws.services.lambda.runtime.events.SQSEvent") + .collect(Collectors.toList()); - /** - * list of events incompatible with Jackson, with serializers explicitly defined - * Classes are incompatible with Jackson for any of the following reasons: - * 1. different constructor/setter types from getter types - * 2. various bugs within Jackson - */ - private static final Map SERIALIZER_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * list of events incompatible with Jackson, with serializers explicitly defined + * Classes are incompatible with Jackson for any of the following reasons: + * 1. different constructor/setter types from getter types + * 2. various bugs within Jackson + */ + private static final Map SERIALIZER_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", + new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", + new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", + new S3EventSerializer<>())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * Maps supported event classes to mixin classes with Jackson annotations. - * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a - * json field that does not match the Jave field name, then a Mixin is required. - */ - @SuppressWarnings("rawtypes") - private static final Map MIXIN_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - CloudFormationCustomResourceEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - CloudFrontEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - CloudWatchLogsEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - CodeCommitEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", - CodeCommitEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - ConnectEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", - ConnectEventMixin.DetailsMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", - ConnectEventMixin.ContactDataMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", - ConnectEventMixin.CustomerEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", - ConnectEventMixin.SystemEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - DynamodbEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - DynamodbEventMixin.DynamodbStreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - DynamodbTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - KinesisEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", - KinesisEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - KinesisTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - ScheduledEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - SecretsManagerRotationEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - SNSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", - SNSEventMixin.SNSRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - SQSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", - SQSEventMixin.SQSMessageMixin.class)) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * Maps supported event classes to mixin classes with Jackson annotations. + * Jackson annotations are not loaded through the ClassLoader so if a Java field + * is serialized or deserialized from a + * json field that does not match the Jave field name, then a Mixin is required. + */ + @SuppressWarnings("rawtypes") + private static final Map MIXIN_MAP = Stream.of( + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + CloudFormationCustomResourceEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + CloudFrontEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + CloudWatchLogsEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + CodeCommitEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", + CodeCommitEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + ConnectEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", + ConnectEventMixin.DetailsMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", + ConnectEventMixin.ContactDataMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", + ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + ConnectEventMixin.QueueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", + ConnectEventMixin.SystemEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + DynamodbEventMixin.class), + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + DynamodbEventMixin.DynamodbStreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + DynamodbTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + KinesisEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", + KinesisEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + KinesisTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + ScheduledEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + SecretsManagerRotationEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + SNSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", + SNSEventMixin.SNSRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + SQSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + SQSEventMixin.SQSMessageMixin.class)) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * If mixins are required for inner classes of an event, then those nested classes must be specified here. - */ - @SuppressWarnings("rawtypes") - private static final Map> NESTED_CLASS_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If mixins are required for inner classes of an event, then those nested + * classes must be specified here. + */ + @SuppressWarnings("rawtypes") + private static final Map> NESTED_CLASS_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>( + "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter - * method setSns, for some magical reasons, using both mixins and a naming strategy works - */ - private static final Map NAMING_STRATEGY_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategies.UpperCamelCaseStrategy()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - new PropertyNamingStrategies.UpperCamelCaseStrategy()) - ) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If event requires a naming strategy. For example, when someone names the + * getter method getSNS and the setter + * method setSns, for some magical reasons, using both mixins and a naming + * strategy works + */ + private static final Map NAMING_STRATEGY_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + new PropertyNamingStrategy.PascalCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategy.PascalCaseStrategy())) + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); - /** - * Returns whether the class name is a Lambda supported event model. - * @param className class name as string - * @return whether the event model is supported - */ - public static boolean isLambdaSupportedEvent(String className) { - return SUPPORTED_EVENTS.contains(className); - } - - /** - * Return a serializer for the event class - * @return a specific PojoSerializer or modified JacksonFactory instance with mixins and modules added in - */ - @SuppressWarnings({"unchecked"}) - public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) { - // if serializer specifically defined for event then use that - if (SERIALIZER_MAP.containsKey(eventClass.getName())) { - return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass).withClassLoader(classLoader); - } - // else use a Jackson ObjectMapper instance - JacksonFactory factory = JacksonFactory.getInstance(); - // if mixins required for class, then apply - if (MIXIN_MAP.containsKey(eventClass.getName())) { - factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName())); + /** + * Returns whether the class name is a Lambda supported event model. + * + * @param className class name as string + * @return whether the event model is supported + */ + public static boolean isLambdaSupportedEvent(String className) { + return SUPPORTED_EVENTS.contains(className); } - // if event model has nested classes then load those classes and check if mixins apply - if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) { - List nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName()); - for (NestedClass nestedClass: nestedClasses) { - // if mixin exists for nested class then apply - if (MIXIN_MAP.containsKey(nestedClass.className)) { - factory = tryLoadingNestedClass(classLoader, factory, nestedClass); + + /** + * Return a serializer for the event class + * + * @return a specific PojoSerializer or modified JacksonFactory instance with + * mixins and modules added in + */ + @SuppressWarnings({ "unchecked" }) + public static PojoSerializer serializerFor(Class eventClass, ClassLoader classLoader) { + // if serializer specifically defined for event then use that + if (SERIALIZER_MAP.containsKey(eventClass.getName())) { + return SERIALIZER_MAP.get(eventClass.getName()).withClass(eventClass) + .withClassLoader(classLoader); } - } - } - // load DateModules - factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader)); - // load naming strategy if needed - if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) { - factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName())); + // else use a Jackson ObjectMapper instance + JacksonFactory factory = JacksonFactory.getInstance(); + // if mixins required for class, then apply + if (MIXIN_MAP.containsKey(eventClass.getName())) { + factory = factory.withMixin(eventClass, MIXIN_MAP.get(eventClass.getName())); + } + // if event model has nested classes then load those classes and check if mixins + // apply + if (NESTED_CLASS_MAP.containsKey(eventClass.getName())) { + List nestedClasses = NESTED_CLASS_MAP.get(eventClass.getName()); + for (NestedClass nestedClass : nestedClasses) { + // if mixin exists for nested class then apply + if (MIXIN_MAP.containsKey(nestedClass.className)) { + factory = tryLoadingNestedClass(classLoader, factory, nestedClass); + } + } + } + // load DateModules + factory.getMapper().registerModules(new DateModule(), new DateTimeModule(classLoader)); + // load naming strategy if needed + if (NAMING_STRATEGY_MAP.containsKey(eventClass.getName())) { + factory = factory.withNamingStrategy(NAMING_STRATEGY_MAP.get(eventClass.getName())); + } + return factory.getSerializer(eventClass); } - return factory.getSerializer(eventClass); - } - /** - * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP} into the {@link JacksonFactory} object. - * Will allow initial failure for {@link AlternateNestedClass} objects and try again with their alternate class name - * @return a modified JacksonFactory instance with mixins added in - */ - private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory, NestedClass nestedClass) { - Class eventClazz; - Class mixinClazz; - try { - eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader); - mixinClazz = MIXIN_MAP.get(nestedClass.getClassName()); - } catch (ReflectUtil.ReflectException e) { - if (nestedClass instanceof AlternateNestedClass) { - AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass; - eventClazz = SerializeUtil.loadCustomerClass(alternateNestedClass.getAlternateClassName(), classLoader); - mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName()); - } else { - throw e; - } - } + /** + * Tries to load a nested class with its defined mixin from {@link #MIXIN_MAP} + * into the {@link JacksonFactory} object. + * Will allow initial failure for {@link AlternateNestedClass} objects and try + * again with their alternate class name + * + * @return a modified JacksonFactory instance with mixins added in + */ + private static JacksonFactory tryLoadingNestedClass(ClassLoader classLoader, JacksonFactory factory, + NestedClass nestedClass) { + Class eventClazz; + Class mixinClazz; + try { + eventClazz = SerializeUtil.loadCustomerClass(nestedClass.getClassName(), classLoader); + mixinClazz = MIXIN_MAP.get(nestedClass.getClassName()); + } catch (ReflectUtil.ReflectException e) { + if (nestedClass instanceof AlternateNestedClass) { + AlternateNestedClass alternateNestedClass = (AlternateNestedClass) nestedClass; + eventClazz = SerializeUtil.loadCustomerClass( + alternateNestedClass.getAlternateClassName(), classLoader); + mixinClazz = MIXIN_MAP.get(alternateNestedClass.getAlternateClassName()); + } else { + throw e; + } + } - return factory.withMixin(eventClazz, mixinClazz); - } + return factory.withMixin(eventClazz, mixinClazz); + } - private static class NestedClass { - private final String className; + private static class NestedClass { + private final String className; - protected NestedClass(String className) { - this.className = className; - } + protected NestedClass(String className) { + this.className = className; + } - protected String getClassName() { - return className; + protected String getClassName() { + return className; + } } - } - private static class AlternateNestedClass extends NestedClass { - private final String alternateClassName; + private static class AlternateNestedClass extends NestedClass { + private final String alternateClassName; - private AlternateNestedClass(String className, String alternateClassName) { - super(className); - this.alternateClassName = alternateClassName; - } + private AlternateNestedClass(String className, String alternateClassName) { + super(className); + this.alternateClassName = alternateClassName; + } - private String getAlternateClassName() { - return alternateClassName; + private String getAlternateClassName() { + return alternateClassName; + } } - } } From c201b4dfcfe2fc663671b6fe27617d0f57d1313e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:38:28 +0000 Subject: [PATCH 109/173] Bump actions/checkout from 5 to 6 (#574) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/repo-sync.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index b1bed919f..270beef8d 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 1f1f08870..d36bdfd13 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 2d101018d..fc4576a9b 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index e9f6a56c1..051498ca7 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index a3afe3729..472c3b57d 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,7 +22,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK uses: actions/setup-java@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 13b7e08b0..fd55b22f2 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index 363647cc4..bea7ff7d8 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 2d97bc868..6a918fde3 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -20,7 +20,7 @@ jobs: env: IS_CONFIGURED: ${{ secrets.SOURCE_REPO != '' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 if: ${{ env.IS_CONFIGURED == 'true' }} - uses: repo-sync/github-sync@v2 name: Sync repo to branch diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 3560207f3..73dd61f89 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index dcad4fa0a..f243afcc6 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -18,7 +18,7 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 @@ -43,7 +43,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index aebb708a7..f8a2f1dd7 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -21,7 +21,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 1.8 uses: actions/setup-java@v4 with: @@ -45,7 +45,7 @@ jobs: custom-serialization: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 uses: actions/setup-java@v4 From d8ffbf1c5e13280b1503bc487816eb33827c0766 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Mon, 23 Mar 2026 22:03:09 +0000 Subject: [PATCH 110/173] Github Actions Improvements (#594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: 🚚 Github Actions improvements * ci: dependent library test fan out * ci: add workflow_dispatch * build: upgrading junit-jupiter everywhere --------- Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 18 ++++-------------- .../aws-lambda-java-events-sdk-transformer.yml | 12 ++++++++---- .github/workflows/aws-lambda-java-events.yml | 14 ++++---------- .github/workflows/aws-lambda-java-log4j2.yml | 13 ++++++++----- .github/workflows/aws-lambda-java-profiler.yml | 1 + .../aws-lambda-java-serialization.yml | 12 ++++++++---- .github/workflows/aws-lambda-java-tests.yml | 14 ++++++-------- .../runtime-interface-client_merge_to_main.yml | 3 ++- .../workflows/runtime-interface-client_pr.yml | 16 +++++++++++----- .github/workflows/samples.yml | 17 ++++++++++++----- .gitignore | 2 +- aws-lambda-java-events-sdk-transformer/pom.xml | 3 ++- aws-lambda-java-events/pom.xml | 3 ++- .../pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- .../examples/cdk/pom.xml | 2 +- samples/kinesis-firehose-event-handler/pom.xml | 3 ++- 17 files changed, 74 insertions(+), 63 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 270beef8d..373acf757 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -1,9 +1,10 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-core -# package and it builds the package and the packages that depend on it. +# package and it builds the package. name: Java CI aws-lambda-java-core on: + workflow_dispatch: push: branches: [ main ] paths: @@ -29,18 +30,7 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + - name: Install core with Maven run: mvn -B install --file aws-lambda-java-core/pom.xml - - # Package modules that depend on base module - - name: Package log4j2 with Maven - run: mvn -B package --file aws-lambda-java-log4j2/pom.xml - - # Test Runtime Interface Client - - name: Run 'pr' target - working-directory: ./aws-lambda-java-runtime-interface-client - run: make pr - env: - IS_JAVA_8: true diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index d36bdfd13..713f97211 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -1,17 +1,21 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-events-sdk-transformer package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-events-sdk-transformer package or its dependency (events), +# and it builds the package. name: Java CI aws-lambda-java-events-sdk-transformer on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-events-sdk-transformer/**' + - 'aws-lambda-java-events/**' - '.github/workflows/aws-lambda-java-events-sdk-transformer.yml' permissions: @@ -29,11 +33,11 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - # Install base module + # Install dependency - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml # Package target module - name: Package events-sdk-transformer with Maven run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml - diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index fc4576a9b..0a8725339 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -1,9 +1,10 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-events -# package and it builds the package and the packages that depend on it. +# package and it builds the package. name: Java CI aws-lambda-java-events on: + workflow_dispatch: push: branches: [ main ] paths: @@ -29,14 +30,7 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - - # Package modules that depend on base module - - name: Package serialization with Maven - run: mvn -B package --file aws-lambda-java-serialization/pom.xml - - name: Package events-sdk-transformer with Maven - run: mvn -B package --file aws-lambda-java-events-sdk-transformer/pom.xml - diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 051498ca7..315678339 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -1,17 +1,20 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-log4j2 package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-log4j2 package or its dependency (core), and it builds the package. name: Java CI aws-lambda-java-log4j2 on: push: + workflow_dispatch: branches: [ main ] paths: - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' - '.github/workflows/aws-lambda-java-log4j2.yml' permissions: @@ -29,11 +32,11 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependency - name: Install core with Maven run: mvn -B install --file aws-lambda-java-core/pom.xml # Package target module - name: Package log4j2 with Maven run: mvn -B package --file aws-lambda-java-log4j2/pom.xml - diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 472c3b57d..1007e7124 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -29,6 +29,7 @@ jobs: with: java-version: 21 distribution: corretto + cache: maven - name: Issue AWS credentials uses: aws-actions/configure-aws-credentials@v4 diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index fd55b22f2..78d23c37b 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -1,17 +1,20 @@ -# This workflow will be triggered if there will be changes to aws-lambda-java-serialization -# package and it builds the package and the packages that depend on it. +# This workflow will be triggered if there will be changes to aws-lambda-java-serialization +# package or its dependency (events), and it builds the package. name: Java CI aws-lambda-java-serialization on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-events/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-events/**' - '.github/workflows/aws-lambda-java-serialization.yml' permissions: @@ -29,8 +32,9 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependency - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index bea7ff7d8..a52181dff 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -1,5 +1,5 @@ # This workflow will be triggered if there will be changes to aws-lambda-java-tests -# package and it builds the package and the packages that depend on it. +# package or its dependencies (events, serialization), and it builds the package. name: Java CI aws-lambda-java-tests @@ -9,16 +9,14 @@ on: branches: [ main ] paths: - 'aws-lambda-java-tests/**' - - 'aws-lambda-java-runtime-interface-client/**' - - 'aws-lambda-java-serialization/**' - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-tests/**' - - 'aws-lambda-java-runtime-interface-client/**' - - 'aws-lambda-java-serialization/**' - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' - '.github/workflows/aws-lambda-java-tests.yml' permissions: @@ -36,8 +34,9 @@ jobs: with: java-version: 8 distribution: corretto - - # Install base module + cache: maven + + # Install dependencies - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - name: Install serialization with Maven @@ -46,4 +45,3 @@ jobs: # Package target module - name: Package tests with Maven run: mvn -B package --file aws-lambda-java-tests/pom.xml - diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index 73dd61f89..da2389d73 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -11,12 +11,12 @@ name: Publish artifact for aws-lambda-java-runtime-interface-client on: + workflow_dispatch: push: branches: [ main ] paths: - 'aws-lambda-java-runtime-interface-client/**' - '.github/workflows/runtime-interface-client_*.yml' - workflow_dispatch: jobs: @@ -35,6 +35,7 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index f243afcc6..59b1c8aed 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -1,13 +1,17 @@ -# This workflow will be triggered if there will be changes to -# aws-lambda-java-runtime-interface-client package and it builds the package. +# This workflow will be triggered if there will be changes to +# aws-lambda-java-runtime-interface-client package or its dependencies (core, serialization), +# and it builds the package. name: PR to runtime-interface-client on: + workflow_dispatch: pull_request: branches: [ '*' ] paths: - 'aws-lambda-java-runtime-interface-client/**' + - 'aws-lambda-java-core/**' + - 'aws-lambda-java-serialization/**' - '.github/workflows/runtime-interface-client_*.yml' permissions: @@ -25,7 +29,8 @@ jobs: with: java-version: 8 distribution: corretto - + cache: maven + - name: Build and install core dependency locally working-directory: ./aws-lambda-java-core run: mvn clean install @@ -39,7 +44,7 @@ jobs: run: make pr env: IS_JAVA_8: true - + build: runs-on: ubuntu-latest steps: @@ -50,6 +55,7 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -61,7 +67,7 @@ jobs: - name: Available buildx platforms run: echo ${{ steps.buildx.outputs.platforms }} - + - name: Build and install core dependency locally working-directory: ./aws-lambda-java-core run: mvn clean install diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index f8a2f1dd7..a76c3fb25 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -1,17 +1,24 @@ -# This workflow will be triggered if there will be changes to aws-lambda-java-core -# package and it builds the package and the packages that depend on it. +# This workflow will be triggered if there will be changes to samples +# or their dependencies (events, serialization, tests). name: Java CI samples on: + workflow_dispatch: push: branches: [ main ] paths: - 'samples/**' + - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-tests/**' pull_request: branches: [ '*' ] paths: - 'samples/**' + - 'aws-lambda-java-events/**' + - 'aws-lambda-java-serialization/**' + - 'aws-lambda-java-tests/**' - '.github/workflows/samples.yml' permissions: @@ -27,14 +34,13 @@ jobs: with: java-version: 8 distribution: corretto + cache: maven - # Install events module + # Install dependencies - name: Install events with Maven run: mvn -B install --file aws-lambda-java-events/pom.xml - # Install serialization module - name: Install serialization with Maven run: mvn -B install --file aws-lambda-java-serialization/pom.xml - # Install tests module - name: Install tests with Maven run: mvn -B install --file aws-lambda-java-tests/pom.xml @@ -54,6 +60,7 @@ jobs: 8 21 distribution: corretto + cache: maven # Install events module using Java 8 - name: Install events with Maven diff --git a/.gitignore b/.gitignore index 5eb5456a1..b5d289a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin /scratch/ .vscode .kiro -build \ No newline at end of file +build diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index d719ec8ac..7c72e98c4 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -38,6 +38,7 @@ 1.8 1.11.914 2.15.40 + 5.14.3 @@ -70,7 +71,7 @@ org.junit.jupiter junit-jupiter-engine - 5.7.0 + ${junit-jupiter.version} test diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 714c825d9..ba93601dc 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -39,6 +39,7 @@ UTF-8 2.20.1 2.40.1 + 5.14.3 @@ -58,7 +59,7 @@ org.junit.jupiter junit-jupiter-engine - 5.9.2 + ${junit-jupiter.version} test diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 65304c61a..f84ffe558 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -37,7 +37,7 @@ 0.8.12 2.4 3.1.1 - 5.9.2 + 5.14.3 3.4.0 + 5.9.2 0.8.7 @@ -250,6 +255,9 @@ org.apache.maven.plugins maven-surefire-plugin 2.22.2 + + true + diff --git a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml index d84b2bd1e..4b46f4e2b 100644 --- a/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml +++ b/experimental/aws-lambda-java-profiler/examples/cdk/pom.xml @@ -11,7 +11,7 @@ UTF-8 2.155.0 [10.0.0,11.0.0) - 5.14.3 + 5.12.2 diff --git a/samples/kinesis-firehose-event-handler/pom.xml b/samples/kinesis-firehose-event-handler/pom.xml index 3bb0051ba..0db8ed83a 100644 --- a/samples/kinesis-firehose-event-handler/pom.xml +++ b/samples/kinesis-firehose-event-handler/pom.xml @@ -35,7 +35,9 @@ 1.8 1.8 UTF-8 - 5.14.3 + 5.12.2 + 3.5.4 + @@ -69,7 +71,10 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + ${maven-surefire-plugin.version} + + true + From 3e23521fbbae085a21610d74055cc3b8b77c2026 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Mon, 30 Mar 2026 15:43:44 +0100 Subject: [PATCH 112/173] Add serialization round-trip tests for event types (#598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 🧪 add roundtrip serialization test utility * test: fix false positives epoch format errors, added comment about this in the serialization package. * test: fixed false positives DateTime differences * test: fixing error in lex event fixture * test: fixing connect event * test: fixing api gateway proxy event false negative * test: fixing CloudFront and S3 event false negatives * build: adding mise to .gitignore * test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures * test: Add round-trip fixtures for 4 registered events * test: Add round-trip tests for 11 response event types * test: including IAM Policy Response roundtrip test * test: add test for JsonNodeUtils * docs: add tests 1.1.2 changelog entry --------- Co-authored-by: Davide Melfi --- .gitignore | 1 + aws-lambda-java-serialization/mise.toml | 2 - .../events/modules/DateModule.java | 15 +- .../lambda/runtime/tests/JsonNodeUtils.java | 110 +++++++++++++ .../runtime/tests/LambdaEventAssert.java | 147 +++++++++++++++++ .../lambda/runtime/tests/EventLoaderTest.java | 15 ++ .../runtime/tests/JsonNodeUtilsTest.java | 155 ++++++++++++++++++ .../runtime/tests/LambdaEventAssertTest.java | 71 ++++++++ ...sponseEventSerializationRoundTripTest.java | 59 +++++++ .../tests/SerializationRoundTripTest.java | 108 ++++++++++++ ...steredEventSerializationRoundTripTest.java | 90 ++++++++++ .../src/test/resources/apigw_http_event.json | 12 -- .../src/test/resources/apigw_rest_event.json | 25 +-- .../test/resources/apigw_websocket_event.json | 88 ++++++++++ .../resources/appsync_authorizer_event.json | 15 ++ .../appsync_authorizer_response.json | 11 ++ .../src/test/resources/cloudfront_event.json | 1 - ...ognito_userpool_create_auth_challenge.json | 37 +++++ .../cognito_userpool_custom_message.json | 28 ++++ ...ognito_userpool_define_auth_challenge.json | 32 ++++ .../cognito_userpool_migrate_user.json | 35 ++++ .../cognito_userpool_postauthentication.json | 20 +++ .../cognito_userpool_postconfirmation.json | 20 +++ ...cognito_userpool_pre_token_generation.json | 48 ++++++ .../cognito_userpool_preauthentication.json | 20 +++ .../cognito/cognito_userpool_presignup.json | 27 +++ ...ognito_userpool_verify_auth_challenge.json | 27 +++ .../test/resources/cognito_sync_event.json | 20 +++ .../src/test/resources/connect_event.json | 9 - .../resources/ddb/dynamo_event_roundtrip.json | 97 +++++++++++ .../ddb/dynamo_time_window_event.json | 75 +++++++++ .../src/test/resources/iot_button_event.json | 5 + .../test/resources/kafka_event_roundtrip.json | 22 +++ ...nalytics_firehose_input_preprocessing.json | 14 ++ ...nalytics_input_preprocessing_response.json | 9 + .../kinesis_analytics_output_delivery.json | 13 ++ ...is_analytics_output_delivery_response.json | 8 + ...analytics_streams_input_preprocessing.json | 17 ++ .../kinesis/kinesis_event_roundtrip.json | 21 +++ .../kinesis/kinesis_time_window_event.json | 32 ++++ .../test/resources/lex_event_roundtrip.json | 24 +++ .../src/test/resources/mq_event.json | 24 +-- .../test/resources/msk_firehose_event.json | 2 +- .../msk_firehose_event_roundtrip.json | 18 ++ .../src/test/resources/partial_pojo.json | 4 + .../resources/rabbitmq_event_roundtrip.json | 51 ++++++ .../test/resources/response/alb_response.json | 15 ++ .../response/apigw_proxy_response.json | 15 ++ .../response/apigw_v2_http_response.json | 16 ++ .../response/apigw_v2_websocket_response.json | 14 ++ .../response/msk_firehose_response.json | 9 + .../resources/response/s3_batch_response.json | 12 ++ .../response/simple_iam_policy_response.json | 7 + .../response/sqs_batch_response.json | 7 + .../src/test/resources/s3_batch_event.json | 15 ++ .../src/test/resources/s3_event.json | 2 + .../resources/s3_object_lambda_event.json | 29 ++++ .../resources/time_window_event_response.json | 10 ++ .../src/test/resources/unstable_pojo.json | 3 + 59 files changed, 1783 insertions(+), 55 deletions(-) delete mode 100644 aws-lambda-java-serialization/mise.toml create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java create mode 100644 aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java create mode 100644 aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json create mode 100644 aws-lambda-java-tests/src/test/resources/cognito_sync_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/iot_button_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/partial_pojo.json create mode 100644 aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/alb_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/s3_batch_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json create mode 100644 aws-lambda-java-tests/src/test/resources/time_window_event_response.json create mode 100644 aws-lambda-java-tests/src/test/resources/unstable_pojo.json diff --git a/.gitignore b/.gitignore index b5d289a8b..5a277e5d6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ experimental/aws-lambda-java-profiler/integration_tests/helloworld/bin .vscode .kiro build +mise.toml diff --git a/aws-lambda-java-serialization/mise.toml b/aws-lambda-java-serialization/mise.toml deleted file mode 100644 index 854b93b56..000000000 --- a/aws-lambda-java-serialization/mise.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tools] -java = "corretto-8" diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java index 8a6954e34..acc8bde2a 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/modules/DateModule.java @@ -15,10 +15,17 @@ import com.fasterxml.jackson.databind.module.SimpleModule; /** - * The AWS API represents a date as a double, which specifies the fractional - * number of seconds since the epoch. Java's Date, however, represents a date as - * a long, which specifies the number of milliseconds since the epoch. This - * class is used to translate between these two formats. + * The AWS API represents a date as a double (fractional seconds since epoch). + * Java's Date uses a long (milliseconds since epoch). This module translates + * between the two formats. + * + *

    + * Round-trip caveats: The serializer always writes via + * {@link JsonGenerator#writeNumber(double)}, so integer epochs + * (e.g. {@code 1428537600}) round-trip as decimal ({@code 1.4285376E9}). + * Sub-millisecond precision is lost because {@link java.util.Date} + * has milliseconds precision. + *

    * * This class is copied from LambdaEventBridgeservice * com.amazon.aws.lambda.stream.ddb.DateModule diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java new file mode 100644 index 000000000..f9f4e1eb6 --- /dev/null +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtils.java @@ -0,0 +1,110 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; +import java.util.regex.Pattern; + +import org.joda.time.DateTime; + +/** + * Utility methods for working with shaded Jackson {@link JsonNode} trees. + * + *

    + * Package-private — not part of the public API. + *

    + */ +class JsonNodeUtils { + + private static final Pattern ISO_DATE_REGEX = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T.+"); + + private JsonNodeUtils() { + } + + /** + * Recursively removes all fields whose value is {@code null} from the + * tree. This mirrors the serializer's {@code Include.NON_NULL} behaviour + * so that explicit nulls in the fixture don't cause false-positive diffs. + */ + static JsonNode stripNulls(JsonNode node) { + if (node.isObject()) { + ObjectNode obj = (ObjectNode) node; + Iterator fieldNames = obj.fieldNames(); + while (fieldNames.hasNext()) { + String field = fieldNames.next(); + if (obj.get(field).isNull()) { + fieldNames.remove(); + } else { + stripNulls(obj.get(field)); + } + } + } else if (node.isArray()) { + for (JsonNode element : node) { + stripNulls(element); + } + } + return node; + } + + /** + * Recursively walks both trees and collects human-readable diff lines. + */ + static void diffNodes(String path, JsonNode expected, JsonNode actual, List diffs) { + if (expected.equals(actual)) + return; + + // Compares two datetime strings by parsed instant, because DateTimeModule + // normalizes the format on serialization (e.g. "+0000" → "Z", "Z" → ".000Z") + if (areSameDateTime(expected.textValue(), actual.textValue())) { + return; + } + + if (expected.isObject() && actual.isObject()) { + TreeSet allKeys = new TreeSet<>(); + expected.fieldNames().forEachRemaining(allKeys::add); + actual.fieldNames().forEachRemaining(allKeys::add); + for (String key : allKeys) { + diffChild(path + "." + key, expected.get(key), actual.get(key), diffs); + } + } else if (expected.isArray() && actual.isArray()) { + for (int i = 0; i < Math.max(expected.size(), actual.size()); i++) { + diffChild(path + "[" + i + "]", expected.get(i), actual.get(i), diffs); + } + } else { + diffs.add("CHANGED " + path + " : " + summarize(expected) + " -> " + summarize(actual)); + } + } + + /** + * Compares two strings by parsed instant when both look like ISO-8601 dates, + * because DateTimeModule normalizes format on serialization + * (e.g. "+0000" → "Z", "Z" → ".000Z"). + */ + private static boolean areSameDateTime(String expected, String actual) { + if (expected == null || actual == null + || !ISO_DATE_REGEX.matcher(expected).matches() + || !ISO_DATE_REGEX.matcher(actual).matches()) { + return false; + } + return DateTime.parse(expected).equals(DateTime.parse(actual)); + } + + private static void diffChild(String path, JsonNode expected, JsonNode actual, List diffs) { + if (expected == null) + diffs.add("ADDED " + path + " = " + summarize(actual)); + else if (actual == null) + diffs.add("MISSING " + path + " (was " + summarize(expected) + ")"); + else + diffNodes(path, expected, actual, diffs); + } + + private static String summarize(JsonNode node) { + if (node == null) { + return ""; + } + String text = node.toString(); + return text.length() > 80 ? text.substring(0, 77) + "..." : text; + } +} diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java new file mode 100644 index 000000000..e189b5e2b --- /dev/null +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java @@ -0,0 +1,147 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; +import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Framework-agnostic assertion utilities for verifying Lambda event + * serialization. + * + *

    + * When opentest4j is on the classpath (e.g. JUnit 5.x / JUnit Platform), + * assertion failures are reported as + * {@code org.opentest4j.AssertionFailedError} + * which enables rich diff support in IDEs. Otherwise, falls back to plain + * {@link AssertionError}. + *

    + * + *

    + * This class is intentionally package-private to support updates to + * the aws-lambda-java-events and aws-lambda-java-serialization packages. + * Consider making it public if there's a real request for it. + *

    + */ +class LambdaEventAssert { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Round-trip using the registered {@link LambdaEventSerializers} path + * (Jackson + mixins + DateModule + DateTimeModule + naming strategies). + * + *

    + * The check performs two consecutive round-trips + * (JSON → POJO → JSON → POJO → JSON) and compares the + * original JSON tree against the final output tree. A single structural + * comparison catches both: + *

    + *
      + *
    • Fields silently dropped during deserialization
    • + *
    • Non-idempotent serialization (output changes across round-trips)
    • + *
    + * + * @param fileName classpath resource name (must end with {@code .json}) + * @param targetClass the event class to deserialize into + * @throws AssertionError if the original and final JSON trees differ + */ + public static void assertSerializationRoundTrip(String fileName, Class targetClass) { + PojoSerializer serializer = LambdaEventSerializers.serializerFor(targetClass, + ClassLoader.getSystemClassLoader()); + + if (!fileName.endsWith(".json")) { + throw new IllegalArgumentException("File " + fileName + " must have json extension"); + } + + byte[] originalBytes; + try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) { + if (stream == null) { + throw new IllegalArgumentException("Could not load resource '" + fileName + "' from classpath"); + } + originalBytes = toBytes(stream); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + fileName, e); + } + + // Two round-trips: original → POJO → JSON → POJO → JSON + // We are doing 2 passes so we can check instability problems + // like UnstablePojo in LambdaEventAssertTest + ByteArrayOutputStream firstOutput = roundTrip(new ByteArrayInputStream(originalBytes), serializer); + ByteArrayOutputStream secondOutput = roundTrip( + new ByteArrayInputStream(firstOutput.toByteArray()), serializer); + + // Compare original tree against final tree. + // Strip explicit nulls from the original because the serializer is + // configured with Include.NON_NULL — null fields are intentionally + // omitted and that is not a data-loss bug. + try { + JsonNode originalTree = JsonNodeUtils.stripNulls(MAPPER.readTree(originalBytes)); + JsonNode finalTree = MAPPER.readTree(secondOutput.toByteArray()); + + if (!originalTree.equals(finalTree)) { + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", originalTree, finalTree, diffs); + + if (!diffs.isEmpty()) { + StringBuilder msg = new StringBuilder(); + msg.append("Serialization round-trip failure for ") + .append(targetClass.getSimpleName()) + .append(" (").append(diffs.size()).append(" difference(s)):\n"); + for (String diff : diffs) { + msg.append(" ").append(diff).append('\n'); + } + + String expected = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(originalTree); + String actual = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(finalTree); + throw buildAssertionError(msg.toString(), expected, actual); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to parse JSON for tree comparison", e); + } + } + + private static ByteArrayOutputStream roundTrip(InputStream stream, PojoSerializer serializer) { + T event = serializer.fromJson(stream); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + serializer.toJson(event, outputStream); + return outputStream; + } + + private static byte[] toBytes(InputStream stream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] chunk = new byte[4096]; + int n; + while ((n = stream.read(chunk)) != -1) { + buffer.write(chunk, 0, n); + } + return buffer.toByteArray(); + } + + /** + * Tries to create an opentest4j AssertionFailedError for rich IDE diff + * support. Falls back to plain AssertionError if opentest4j is not on + * the classpath. + */ + private static AssertionError buildAssertionError(String message, String expected, String actual) { + try { + // opentest4j is provided by JUnit Platform (5.x) and enables + // IDE diff viewers to show expected vs actual side-by-side. + Class cls = Class.forName("org.opentest4j.AssertionFailedError"); + return (AssertionError) cls + .getConstructor(String.class, Object.class, Object.class) + .newInstance(message, expected, actual); + } catch (ReflectiveOperationException e) { + return new AssertionError(message + "\nExpected:\n" + expected + "\nActual:\n" + actual); + } + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java index 752b84e27..43030bbca 100644 --- a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/EventLoaderTest.java @@ -81,6 +81,12 @@ public void testLoadAPIGatewayV2CustomAuthorizerEvent() { assertThat(event).isNotNull(); assertThat(event.getRequestContext().getHttp().getMethod()).isEqualTo("POST"); + // getTime() converts the raw string "12/Mar/2020:19:03:58 +0000" into a DateTime object; + // Jackson then serializes it as ISO-8601 "2020-03-12T19:03:58.000Z" + assertThat(event.getRequestContext().getTime().toInstant().getMillis()) + .isEqualTo(DateTime.parse("2020-03-12T19:03:58.000Z").toInstant().getMillis()); + // getTimeEpoch() converts the raw long into an Instant; + // Jackson then serializes it as a decimal seconds value assertThat(event.getRequestContext().getTimeEpoch()).isEqualTo(Instant.ofEpochMilli(1583348638390L)); } @@ -136,6 +142,9 @@ public void testLoadLexEvent() { assertThat(event.getCurrentIntent().getName()).isEqualTo("BookHotel"); assertThat(event.getCurrentIntent().getSlots()).hasSize(4); assertThat(event.getBot().getName()).isEqualTo("BookTrip"); + // Jackson leniently coerces the JSON number for "Nights" into a String + // because slots is typed as Map + assertThat(event.getCurrentIntent().getSlots().get("Nights")).isInstanceOf(String.class); } @Test @@ -159,6 +168,10 @@ public void testLoadMSKFirehoseEvent() { assertThat(event.getRecords().get(0).getKafkaRecordValue().array()).asString().isEqualTo("{\"Name\":\"Hello World\"}"); assertThat(event.getRecords().get(0).getApproximateArrivalTimestamp()).asString().isEqualTo("1716369573887"); assertThat(event.getRecords().get(0).getMskRecordMetadata()).asString().isEqualTo("{offset=0, partitionId=1, approximateArrivalTimestamp=1716369573887}"); + // Jackson leniently coerces the JSON number in mskRecordMetadata into a String + // because the map is typed as Map + Map metadata = event.getRecords().get(0).getMskRecordMetadata(); + assertThat(metadata.get("approximateArrivalTimestamp")).isInstanceOf(String.class); } @Test @@ -408,6 +421,8 @@ public void testLoadRabbitMQEvent() { .returns("AIDACKCEVSQ6C2EXAMPLE", from(RabbitMQEvent.BasicProperties::getUserId)) .returns(80, from(RabbitMQEvent.BasicProperties::getBodySize)) .returns("Jan 1, 1970, 12:33:41 AM", from(RabbitMQEvent.BasicProperties::getTimestamp)); + // Jackson leniently coerces the JSON string "60000" for expiration into int + // because the model field is typed as int Map headers = basicProperties.getHeaders(); assertThat(headers).hasSize(3); diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java new file mode 100644 index 000000000..ec5798dca --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/JsonNodeUtilsTest.java @@ -0,0 +1,155 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.JsonNode; +import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class JsonNodeUtilsTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + // --- stripNulls --- + + @Test + void stripNulls_removesTopLevelNulls() throws Exception { + JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":null,\"c\":\"hello\"}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"a\":1,\"c\":\"hello\"}"), result); + } + + @Test + void stripNulls_removesNestedNulls() throws Exception { + JsonNode node = MAPPER.readTree("{\"outer\":{\"keep\":true,\"drop\":null}}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"outer\":{\"keep\":true}}"), result); + } + + @Test + void stripNulls_leavesArrayElementsIntact() throws Exception { + // Nulls inside arrays are kept (they're positional) + JsonNode node = MAPPER.readTree("{\"arr\":[1,null,3]}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"arr\":[1,null,3]}"), result); + } + + @Test + void stripNulls_removesNullsInsideArrayObjects() throws Exception { + JsonNode node = MAPPER.readTree("[{\"a\":1,\"b\":null},{\"c\":null}]"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("[{\"a\":1},{}]"), result); + } + + @Test + void stripNulls_noOpOnCleanTree() throws Exception { + JsonNode node = MAPPER.readTree("{\"a\":1,\"b\":\"two\"}"); + JsonNode result = JsonNodeUtils.stripNulls(node); + assertEquals(MAPPER.readTree("{\"a\":1,\"b\":\"two\"}"), result); + } + + // --- diffNodes --- + + @Test + void diffNodes_identicalTrees_noDiffs() throws Exception { + JsonNode a = MAPPER.readTree("{\"x\":1,\"y\":\"hello\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", a, a.deepCopy(), diffs); + assertTrue(diffs.isEmpty()); + } + + @Test + void diffNodes_changedValue() throws Exception { + JsonNode expected = MAPPER.readTree("{\"x\":1}"); + JsonNode actual = MAPPER.readTree("{\"x\":2}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).startsWith("CHANGED .x")); + } + + @Test + void diffNodes_missingField() throws Exception { + JsonNode expected = MAPPER.readTree("{\"a\":1,\"b\":2}"); + JsonNode actual = MAPPER.readTree("{\"a\":1}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("MISSING") && diffs.get(0).contains(".b"), "got: " + diffs.get(0)); + } + + @Test + void diffNodes_addedField() throws Exception { + JsonNode expected = MAPPER.readTree("{\"a\":1}"); + JsonNode actual = MAPPER.readTree("{\"a\":1,\"b\":2}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size(), "diffs: " + diffs); + assertTrue(diffs.get(0).contains("ADDED") && diffs.get(0).contains(".b"), "got: " + diffs.get(0)); + } + + @Test + void diffNodes_nestedObjectDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"outer\":{\"inner\":\"old\"}}"); + JsonNode actual = MAPPER.readTree("{\"outer\":{\"inner\":\"new\"}}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains(".outer.inner")); + } + + @Test + void diffNodes_arrayElementDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"arr\":[1,2,3]}"); + JsonNode actual = MAPPER.readTree("{\"arr\":[1,99,3]}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("[1]")); + } + + @Test + void diffNodes_arrayLengthMismatch() throws Exception { + JsonNode expected = MAPPER.readTree("{\"arr\":[1,2]}"); + JsonNode actual = MAPPER.readTree("{\"arr\":[1,2,3]}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).contains("ADDED"), "got: " + diffs.get(0)); + } + + // --- areSameDateTime (tested indirectly via diffNodes) --- + + @Test + void diffNodes_equivalentDateTimes_noDiff() throws Exception { + // "+0000" vs "Z" — same instant, different format + JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000+0000\"}"); + JsonNode actual = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertTrue(diffs.isEmpty(), "Expected no diffs for equivalent datetimes, got: " + diffs); + } + + @Test + void diffNodes_differentDateTimes_hasDiff() throws Exception { + JsonNode expected = MAPPER.readTree("{\"t\":\"2020-03-12T19:03:58.000Z\"}"); + JsonNode actual = MAPPER.readTree("{\"t\":\"2021-01-01T00:00:00.000Z\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + } + + @Test + void diffNodes_nonDateStrings_notTreatedAsDates() throws Exception { + JsonNode expected = MAPPER.readTree("{\"s\":\"hello\"}"); + JsonNode actual = MAPPER.readTree("{\"s\":\"world\"}"); + List diffs = new ArrayList<>(); + JsonNodeUtils.diffNodes("", expected, actual, diffs); + assertEquals(1, diffs.size()); + assertTrue(diffs.get(0).startsWith("CHANGED .s")); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java new file mode 100644 index 000000000..63067f8b2 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssertTest.java @@ -0,0 +1,71 @@ +package com.amazonaws.services.lambda.runtime.tests; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LambdaEventAssertTest { + /** + * Demonstrates the completeness check: the fixture has a field + * ({@code unknownField}) that {@code PartialPojo} does not capture, + * so it gets silently dropped during deserialization. + */ + @Test + void shouldFailWhenFieldIsDropped() { + AssertionError error = assertThrows(AssertionError.class, + () -> LambdaEventAssert.assertSerializationRoundTrip( + "partial_pojo.json", PartialPojo.class)); + + assertTrue(error.getMessage().contains("PartialPojo"), + "Error message should name the failing class"); + } + + /** + * Demonstrates the stability check: the getter mutates state on each + * call, so the first and second round-trips produce different JSON. + */ + @Test + void shouldFailWhenSerializationIsUnstable() { + AssertionError error = assertThrows(AssertionError.class, + () -> LambdaEventAssert.assertSerializationRoundTrip( + "unstable_pojo.json", UnstablePojo.class)); + + assertTrue(error.getMessage().contains("UnstablePojo"), + "Error message should name the failing class"); + } + + /** POJO that only captures {@code name}, silently dropping any other fields. */ + public static class PartialPojo { + private String name; + + public PartialPojo() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + /** + * POJO with a getter that appends a suffix, making serialization + * non-idempotent. + */ + public static class UnstablePojo { + private String name; + + public UnstablePojo() { + } + + public String getName() { + return name == null ? null : name + "_x"; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java new file mode 100644 index 000000000..e700b0d01 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/ResponseEventSerializationRoundTripTest.java @@ -0,0 +1,59 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * Verifies serialization round-trip fidelity for Lambda response event types. + * + *

    Response events are POJOs that Lambda functions return to the RIC, which + * serializes them to JSON via {@code EventHandlerLoader.getSerializer()}. + * None of these types are registered in {@code SUPPORTED_EVENTS}, so they + * go through the bare Jackson path ({@code JacksonFactory.getInstance() + * .getSerializer(type)}).

    + * + *

    Although the RIC only calls {@code toJson()} on response types (never + * {@code fromJson()}), the round-trip test is a stricter check: if a response + * type survives JSON → POJO → JSON → POJO → JSON, it + * certainly survives the production POJO → JSON path.

    + * + * @see SerializationRoundTripTest for registered input events + * @see UnregisteredEventSerializationRoundTripTest for unregistered input events + */ +@SuppressWarnings("deprecation") // APIGatewayV2ProxyResponseEvent is deprecated +public class ResponseEventSerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + private static Stream passingCases() { + return Stream.of( + // API Gateway responses + args(APIGatewayProxyResponseEvent.class, "response/apigw_proxy_response.json"), + args(APIGatewayV2HTTPResponse.class, "response/apigw_v2_http_response.json"), + args(APIGatewayV2WebSocketResponse.class, "response/apigw_v2_websocket_response.json"), + args(APIGatewayV2ProxyResponseEvent.class, "response/apigw_v2_websocket_response.json"), + // ALB response + args(ApplicationLoadBalancerResponseEvent.class, "response/alb_response.json"), + // S3 Batch response + args(S3BatchResponse.class, "response/s3_batch_response.json"), + // SQS Batch response + args(SQSBatchResponse.class, "response/sqs_batch_response.json"), + // Simple IAM Policy response (HTTP API) + args(SimpleIAMPolicyResponse.class, "response/simple_iam_policy_response.json"), + // MSK Firehose response + args(MSKFirehoseResponse.class, "response/msk_firehose_response.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java new file mode 100644 index 000000000..3eab8fec0 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/SerializationRoundTripTest.java @@ -0,0 +1,108 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies serialization round-trip fidelity for events that are registered in + * {@code LambdaEventSerializers.SUPPORTED_EVENTS}. + * + *

    Registered events go through the full customized serialization path in the + * Runtime Interface Client (RIC): {@code EventHandlerLoader.getSerializer()} + * detects them via {@code isLambdaSupportedEvent()} and delegates to + * {@code LambdaEventSerializers.serializerFor()}, which applies Jackson mixins, + * {@code DateModule}/{@code DateTimeModule}, and naming strategies.

    + * + *

    Each case feeds a JSON fixture through + * {@link LambdaEventAssert#assertSerializationRoundTrip} which performs two + * consecutive round-trips and compares the original JSON tree against the + * final output.

    + * + * @see UnregisteredEventSerializationRoundTripTest for events not in SUPPORTED_EVENTS + */ +public class SerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + @ParameterizedTest(name = "{0} (known failure)") + @MethodSource("knownFailureCases") + void roundTripKnownFailures(String displayName, String fixture, Class eventClass) { + assertThrows(Throwable.class, + () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass), + displayName + " was expected to fail but passed — move it to passingCases()"); + } + + private static Stream passingCases() { + return Stream.of( + args(CloudFormationCustomResourceEvent.class, "cloudformation_event.json"), + args(CloudWatchLogsEvent.class, "cloudwatchlogs_event.json"), + args(CodeCommitEvent.class, "codecommit_event.json"), + args(ConfigEvent.class, "config_event.json"), + args(DynamodbEvent.class, "ddb/dynamo_event_roundtrip.json"), + args(KinesisEvent.class, "kinesis/kinesis_event_roundtrip.json"), + args(KinesisFirehoseEvent.class, "firehose_event.json"), + args(LambdaDestinationEvent.class, "lambda_destination_event.json"), + args(ScheduledEvent.class, "cloudwatch_event.json"), + args(SecretsManagerRotationEvent.class, "secrets_rotation_event.json"), + args(SNSEvent.class, "sns_event.json"), + args(LexEvent.class, "lex_event_roundtrip.json"), + args(ConnectEvent.class, "connect_event.json"), + args(SQSEvent.class, "sqs/sqs_event_nobody.json"), + args(APIGatewayProxyRequestEvent.class, "apigw_rest_event.json"), + args(CloudFrontEvent.class, "cloudfront_event.json"), + args(S3Event.class, "s3_event.json"), + args(S3EventNotification.class, "s3_event.json"), + args(APIGatewayV2HTTPEvent.class, "apigw_http_event.json"), + args(APIGatewayCustomAuthorizerEvent.class, "apigw_auth.json"), + args(ApplicationLoadBalancerRequestEvent.class, "elb_event.json"), + args(CloudWatchCompositeAlarmEvent.class, "cloudwatch_composite_alarm.json"), + args(CloudWatchMetricAlarmEvent.class, "cloudwatch_metric_alarm.json"), + args(CognitoUserPoolPreTokenGenerationEventV2.class, "cognito_user_pool_pre_token_generation_event_v2.json"), + args(KafkaEvent.class, "kafka_event_roundtrip.json"), + args(MSKFirehoseEvent.class, "msk_firehose_event_roundtrip.json"), + args(RabbitMQEvent.class, "rabbitmq_event_roundtrip.json"), + args(S3BatchEventV2.class, "s3_batch_event_v2.json"), + args(IoTButtonEvent.class, "iot_button_event.json"), + args(CognitoEvent.class, "cognito_sync_event.json"), + args(DynamodbTimeWindowEvent.class, "ddb/dynamo_time_window_event.json"), + args(KinesisTimeWindowEvent.class, "kinesis/kinesis_time_window_event.json")); + } + + private static Stream knownFailureCases() { + return Stream.of( + // APIGatewayV2CustomAuthorizerEvent has two serialization issues: + // 1. getTime() parses the raw string "12/Mar/2020:19:03:58 +0000" into a + // DateTime via dd/MMM/yyyy formatter. Jackson serializes as ISO-8601, but + // the formatter cannot parse ISO-8601 back on the second round-trip. + // The time field is effectively mandatory (getTime() throws NPE if null), + // and the date format change is inherent to how the serialization works. + // 2. getTimeEpoch() converts long to Instant; Jackson serializes as decimal + // seconds (e.g. 1583348638.390000000) instead of the original long. + // Both transformations are lossy; coercion captured in EventLoaderTest. + args(APIGatewayV2CustomAuthorizerEvent.class, "apigw_auth_v2.json"), + // ActiveMQEvent has one serialization issue: + // Destination.physicalName (camelCase) vs JSON "physicalname" (lowercase) — + // ACCEPT_CASE_INSENSITIVE_PROPERTIES is disabled in JacksonFactory so the + // field is silently dropped during deserialization. + // Fix: create an ActiveMQEventMixin with a DestinationMixin that maps + // @JsonProperty("physicalname") to getPhysicalName()/setPhysicalName(), + // then register it in LambdaEventSerializers MIXIN_MAP and NESTED_CLASS_MAP. + args(ActiveMQEvent.class, "mq_event.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java new file mode 100644 index 000000000..12ad818e4 --- /dev/null +++ b/aws-lambda-java-tests/src/test/java/com/amazonaws/services/lambda/runtime/tests/UnregisteredEventSerializationRoundTripTest.java @@ -0,0 +1,90 @@ +/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +package com.amazonaws.services.lambda.runtime.tests; + +import com.amazonaws.services.lambda.runtime.events.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies serialization round-trip fidelity for events that are NOT registered + * in {@code LambdaEventSerializers.SUPPORTED_EVENTS}. + * + *

    In the Runtime Interface Client (RIC), when a handler's event type is not + * in {@code SUPPORTED_EVENTS}, {@code EventHandlerLoader.getSerializer()} falls + * through to {@code JacksonFactory.getInstance().getSerializer(type)} — a bare + * {@code PojoSerializer} backed by Jackson without any mixins or naming + * strategies. However, {@code LambdaEventSerializers.serializerFor()} (used by + * this test) unconditionally registers {@code DateModule} and + * {@code DateTimeModule}, so Joda/java.time types are still handled. For most + * unregistered events this makes no practical difference because they don't + * contain Joda DateTime fields.

    + * + * @see SerializationRoundTripTest for events registered in SUPPORTED_EVENTS + */ +@SuppressWarnings("deprecation") // APIGatewayV2ProxyRequestEvent is deprecated +public class UnregisteredEventSerializationRoundTripTest { + + @ParameterizedTest(name = "{0}") + @MethodSource("passingCases") + void roundTrip(String displayName, String fixture, Class eventClass) { + LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass); + } + + @ParameterizedTest(name = "{0} (known failure)") + @MethodSource("knownFailureCases") + void roundTripKnownFailures(String displayName, String fixture, Class eventClass) { + assertThrows(Throwable.class, + () -> LambdaEventAssert.assertSerializationRoundTrip(fixture, eventClass), + displayName + " was expected to fail but passed — move it to passingCases()"); + } + + private static Stream passingCases() { + return Stream.of( + // S3 Batch + args(S3BatchEvent.class, "s3_batch_event.json"), + // AppSync + args(AppSyncLambdaAuthorizerEvent.class, "appsync_authorizer_event.json"), + args(AppSyncLambdaAuthorizerResponse.class, "appsync_authorizer_response.json"), + // TimeWindow response + args(TimeWindowEventResponse.class, "time_window_event_response.json"), + // Cognito UserPool triggers + args(CognitoUserPoolPreSignUpEvent.class, "cognito/cognito_userpool_presignup.json"), + args(CognitoUserPoolPostConfirmationEvent.class, "cognito/cognito_userpool_postconfirmation.json"), + args(CognitoUserPoolPreAuthenticationEvent.class, "cognito/cognito_userpool_preauthentication.json"), + args(CognitoUserPoolPostAuthenticationEvent.class, "cognito/cognito_userpool_postauthentication.json"), + args(CognitoUserPoolDefineAuthChallengeEvent.class, "cognito/cognito_userpool_define_auth_challenge.json"), + args(CognitoUserPoolCreateAuthChallengeEvent.class, "cognito/cognito_userpool_create_auth_challenge.json"), + args(CognitoUserPoolVerifyAuthChallengeResponseEvent.class, "cognito/cognito_userpool_verify_auth_challenge.json"), + args(CognitoUserPoolMigrateUserEvent.class, "cognito/cognito_userpool_migrate_user.json"), + args(CognitoUserPoolCustomMessageEvent.class, "cognito/cognito_userpool_custom_message.json"), + args(CognitoUserPoolPreTokenGenerationEvent.class, "cognito/cognito_userpool_pre_token_generation.json"), + // Kinesis Analytics + args(KinesisAnalyticsFirehoseInputPreprocessingEvent.class, "kinesis/kinesis_analytics_firehose_input_preprocessing.json"), + args(KinesisAnalyticsStreamsInputPreprocessingEvent.class, "kinesis/kinesis_analytics_streams_input_preprocessing.json"), + args(KinesisAnalyticsInputPreprocessingResponse.class, "kinesis/kinesis_analytics_input_preprocessing_response.json"), + args(KinesisAnalyticsOutputDeliveryEvent.class, "kinesis/kinesis_analytics_output_delivery.json"), + args(KinesisAnalyticsOutputDeliveryResponse.class, "kinesis/kinesis_analytics_output_delivery_response.json"), + // API Gateway V2 WebSocket + args(APIGatewayV2WebSocketEvent.class, "apigw_websocket_event.json"), + args(APIGatewayV2ProxyRequestEvent.class, "apigw_websocket_event.json")); + } + + private static Stream knownFailureCases() { + return Stream.of( + // S3ObjectLambdaEvent: Lombok generates getXAmzRequestId() for field + // "xAmzRequestId". With USE_STD_BEAN_NAMING, Jackson derives the property + // name as "XAmzRequestId" (capital X), so the original "xAmzRequestId" key + // is silently dropped during deserialization. + // Fix: add @JsonProperty("xAmzRequestId") on the field or getter. + args(S3ObjectLambdaEvent.class, "s3_object_lambda_event.json")); + } + + private static Arguments args(Class clazz, String fixture) { + return Arguments.of(clazz.getSimpleName(), fixture, clazz); + } +} diff --git a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json index 88f4e5b4b..91954656c 100644 --- a/aws-lambda-java-tests/src/test/resources/apigw_http_event.json +++ b/aws-lambda-java-tests/src/test/resources/apigw_http_event.json @@ -18,18 +18,6 @@ "requestContext": { "accountId": "123456789012", "apiId": "api-id", - "authentication": { - "clientCert": { - "clientCertPem": "CERT_CONTENT", - "subjectDN": "www.example.com", - "issuerDN": "Example issuer", - "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", - "validity": { - "notBefore": "May 28 12:30:02 2019 GMT", - "notAfter": "Aug 5 09:36:04 2021 GMT" - } - } - }, "authorizer": { "jwt": { "claims": { diff --git a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json index 28f10c221..a139ccbe8 100644 --- a/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json +++ b/aws-lambda-java-tests/src/test/resources/apigw_rest_event.json @@ -14,19 +14,22 @@ "Header2": [ "value1", "value2" + ], + "Header3": [ + "value1,value2" ] }, "queryStringParameters": { "parameter1": "value1", - "parameter2": "value" + "parameter2": "value2" }, "multiValueQueryStringParameters": { "parameter1": [ - "value1", - "value2" + "value1" ], "parameter2": [ - "value" + "value1", + "value2" ] }, "requestContext": { @@ -52,17 +55,7 @@ "sourceIp": "IP", "user": null, "userAgent": "user-agent", - "userArn": null, - "clientCert": { - "clientCertPem": "CERT_CONTENT", - "subjectDN": "www.example.com", - "issuerDN": "Example issuer", - "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", - "validity": { - "notBefore": "May 28 12:30:02 2019 GMT", - "notAfter": "Aug 5 09:36:04 2021 GMT" - } - } + "userArn": null }, "path": "/my/path", "protocol": "HTTP/1.1", @@ -76,5 +69,5 @@ "pathParameters": null, "stageVariables": null, "body": "Hello from Lambda!", - "isBase64Encoded": true + "isBase64Encoded": false } \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json new file mode 100644 index 000000000..a47fc3a94 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/apigw_websocket_event.json @@ -0,0 +1,88 @@ +{ + "resource": "/", + "path": "/", + "httpMethod": "GET", + "headers": { + "Host": "abcdef1234.execute-api.us-east-1.amazonaws.com", + "Sec-WebSocket-Extensions": "permessage-deflate", + "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==", + "Sec-WebSocket-Version": "13", + "X-Forwarded-For": "192.0.2.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ + "abcdef1234.execute-api.us-east-1.amazonaws.com" + ], + "Sec-WebSocket-Extensions": [ + "permessage-deflate" + ], + "Sec-WebSocket-Key": [ + "dGhlIHNhbXBsZSBub25jZQ==" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Forwarded-For": [ + "192.0.2.1" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "param1": "value1" + }, + "multiValueQueryStringParameters": { + "param1": [ + "value1" + ] + }, + "pathParameters": { + "proxy": "path/to/resource" + }, + "stageVariables": { + "stageVar1": "value1" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "abcdef", + "stage": "prod", + "requestId": "abc-def-ghi", + "identity": { + "cognitoIdentityPoolId": "us-east-1:id-pool", + "accountId": "123456789012", + "cognitoIdentityId": "us-east-1:identity-id", + "caller": "caller-id", + "apiKey": "api-key-id", + "sourceIp": "192.0.2.1", + "cognitoAuthenticationType": "authenticated", + "cognitoAuthenticationProvider": "provider", + "userArn": "arn:aws:iam::123456789012:user/testuser", + "userAgent": "Mozilla/5.0", + "user": "testuser", + "accessKey": "AKIAIOSFODNN7EXAMPLE" + }, + "resourcePath": "/", + "httpMethod": "GET", + "apiId": "abcdef1234", + "connectedAt": 1583348638390, + "connectionId": "abc123=", + "domainName": "abcdef1234.execute-api.us-east-1.amazonaws.com", + "eventType": "CONNECT", + "extendedRequestId": "abc123=", + "integrationLatency": "100", + "messageDirection": "IN", + "messageId": "msg-001", + "requestTime": "09/Apr/2020:18:03:58 +0000", + "requestTimeEpoch": 1583348638390, + "routeKey": "$connect", + "status": "200" + }, + "body": "request body", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json new file mode 100644 index 000000000..494a500f3 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_event.json @@ -0,0 +1,15 @@ +{ + "authorizationToken": "BE9DC5E3-D410-4733-AF76-70178092E681", + "requestContext": { + "apiId": "giy7kumfmvcqvbedntjwjvagii", + "accountId": "254688921111", + "requestId": "b80ed838-14c6-4500-b4c3-b694c7bef086", + "queryDocument": "mutation MyNewTask($desc: String!) {\n createTask(description: $desc) {\n id\n }\n}\n", + "operationName": "MyNewTask", + "variables": {} + }, + "requestHeaders": { + "host": "giy7kumfmvcqvbedntjwjvagii.appsync-api.us-east-1.amazonaws.com", + "content-type": "application/json" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json new file mode 100644 index 000000000..1216ad51d --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/appsync_authorizer_response.json @@ -0,0 +1,11 @@ +{ + "isAuthorized": true, + "resolverContext": { + "name": "Foo Man", + "balance": "100" + }, + "deniedFields": [ + "Mutation.createEvent" + ], + "ttlOverride": 15 +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json index 7485310e9..bf4625d06 100644 --- a/aws-lambda-java-tests/src/test/resources/cloudfront_event.json +++ b/aws-lambda-java-tests/src/test/resources/cloudfront_event.json @@ -7,7 +7,6 @@ }, "request": { "uri": "/test", - "querystring": "auth=test&foo=bar", "method": "GET", "clientIp": "2001:cdba::3257:9652", "headers": { diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json new file mode 100644 index 000000000..495b41475 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_create_auth_challenge.json @@ -0,0 +1,37 @@ +{ + "version": "1", + "triggerSource": "CreateAuthChallenge_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "challengeName": "CUSTOM_CHALLENGE", + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata1" + } + ], + "userNotFound": false + }, + "response": { + "publicChallengeParameters": { + "captchaUrl": "url/123.jpg" + }, + "privateChallengeParameters": { + "answer": "5" + }, + "challengeMetadata": "CAPTCHA_CHALLENGE" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json new file mode 100644 index 000000000..aa7a53a83 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_custom_message.json @@ -0,0 +1,28 @@ +{ + "version": "1", + "triggerSource": "CustomMessage_SignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com", + "phone_number_verified": "true", + "email_verified": "true" + }, + "clientMetadata": { + "meta1": "value1" + }, + "codeParameter": "####", + "usernameParameter": "testuser" + }, + "response": { + "smsMessage": "Your code is ####", + "emailMessage": "Your code is ####", + "emailSubject": "Welcome" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json new file mode 100644 index 000000000..e320b71d1 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_define_auth_challenge.json @@ -0,0 +1,32 @@ +{ + "version": "1", + "triggerSource": "DefineAuthChallenge_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata1" + } + ], + "userNotFound": false + }, + "response": { + "challengeName": "CUSTOM_CHALLENGE", + "issueTokens": false, + "failAuthentication": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json new file mode 100644 index 000000000..2897ae063 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_migrate_user.json @@ -0,0 +1,35 @@ +{ + "version": "1", + "triggerSource": "UserMigration_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "clientMetadata": { + "meta1": "value1" + }, + "userName": "testuser", + "password": "test-password" + }, + "response": { + "userAttributes": { + "email": "user@example.com" + }, + "finalUserStatus": "CONFIRMED", + "messageAction": "SUPPRESS", + "desiredDeliveryMediums": [ + "EMAIL" + ], + "forceAliasCreation": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json new file mode 100644 index 000000000..f41084d54 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postauthentication.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PostAuthentication_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "newDeviceUsed": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json new file mode 100644 index 000000000..ecf63c7d3 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_postconfirmation.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PostConfirmation_ConfirmSignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com", + "email_verified": "true" + }, + "clientMetadata": { + "meta1": "value1" + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json new file mode 100644 index 000000000..f81ffb902 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_pre_token_generation.json @@ -0,0 +1,48 @@ +{ + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "groupConfiguration": { + "groupsToOverride": [ + "group1", + "group2" + ], + "iamRolesToOverride": [ + "arn:aws:iam::123456789012:role/role1" + ], + "preferredRole": "arn:aws:iam::123456789012:role/role1" + } + }, + "response": { + "claimsOverrideDetails": { + "claimsToAddOrOverride": { + "custom:myattr": "myvalue" + }, + "claimsToSuppress": [ + "email" + ], + "groupOverrideDetails": { + "groupsToOverride": [ + "group1" + ], + "iamRolesToOverride": [ + "arn:aws:iam::123456789012:role/role1" + ], + "preferredRole": "arn:aws:iam::123456789012:role/role1" + } + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json new file mode 100644 index 000000000..1402c4684 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_preauthentication.json @@ -0,0 +1,20 @@ +{ + "version": "1", + "triggerSource": "PreAuthentication_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "userNotFound": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json new file mode 100644 index 000000000..0d1f0936a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_presignup.json @@ -0,0 +1,27 @@ +{ + "version": "1", + "triggerSource": "PreSignUp_SignUp", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "validationData": { + "key1": "val1" + }, + "clientMetadata": { + "meta1": "value1" + } + }, + "response": { + "autoConfirmUser": false, + "autoVerifyPhone": false, + "autoVerifyEmail": false + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json new file mode 100644 index 000000000..ef14c4ddf --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito/cognito_userpool_verify_auth_challenge.json @@ -0,0 +1,27 @@ +{ + "version": "1", + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "region": "us-east-1", + "userPoolId": "us-east-1_uPoolId", + "userName": "testuser", + "callerContext": { + "awsSdkVersion": "2.0.0", + "clientId": "abcdefg1234567" + }, + "request": { + "userAttributes": { + "email": "user@example.com" + }, + "clientMetadata": { + "meta1": "value1" + }, + "privateChallengeParameters": { + "answer": "5" + }, + "challengeAnswer": "5", + "userNotFound": false + }, + "response": { + "answerCorrect": true + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json new file mode 100644 index 000000000..6edf1c246 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/cognito_sync_event.json @@ -0,0 +1,20 @@ +{ + "version": 2, + "eventType": "SyncTrigger", + "region": "us-east-1", + "identityPoolId": "us-east-1:example-identity-pool-id", + "identityId": "us-east-1:example-identity-id", + "datasetName": "SampleDataset", + "datasetRecords": { + "SampleKey1": { + "oldValue": "oldValue1", + "newValue": "newValue1", + "op": "replace" + }, + "SampleKey2": { + "oldValue": "oldValue2", + "newValue": "newValue2", + "op": "replace" + } + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/connect_event.json b/aws-lambda-java-tests/src/test/resources/connect_event.json index b71bf6692..4ce17a657 100644 --- a/aws-lambda-java-tests/src/test/resources/connect_event.json +++ b/aws-lambda-java-tests/src/test/resources/connect_event.json @@ -12,15 +12,6 @@ "InitialContactId": "6ca32fbd-8f92-46af-92a5-6b0f970f0efe", "InitiationMethod": "API", "InstanceARN": "arn:aws:connect:eu-central-1:123456789012:instance/9308c2a1-9bc6-4cea-8290-6c0b4a6d38fa", - "MediaStreams": { - "Customer": { - "Audio": { - "StartFragmentNumber": "91343852333181432392682062622220590765191907586", - "StartTimestamp": "1565781909613", - "StreamARN": "arn:aws:kinesisvideo:eu-central-1:123456789012:stream/connect-contact-a3d73b84-ce0e-479a-a9dc-5637c9d30ac9/1565272947806" - } - } - }, "PreviousContactId": "4ca32fbd-8f92-46af-92a5-6b0f970f0efe", "Queue": { "Name": "SampleQueue", diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json new file mode 100644 index 000000000..10d963c3c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_event_roundtrip.json @@ -0,0 +1,97 @@ +{ + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.4285376E9, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "userIdentity": { + "principalId": "dynamodb.amazonaws.com", + "type": "Service" + } + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.635734407123E9, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "eu-central-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1.4285376E9, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:eu-central-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json new file mode 100644 index 000000000..d931acb80 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/ddb/dynamo_time_window_event.json @@ -0,0 +1,75 @@ +{ + "Records": [ + { + "eventID": "1", + "eventName": "INSERT", + "eventVersion": "1.0", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "SequenceNumber": "111", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "2", + "eventName": "MODIFY", + "eventVersion": "1.0", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "SequenceNumber": "222", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899" + } + ], + "window": { + "start": "2020-07-30T17:00:00Z", + "end": "2020-07-30T17:05:00Z" + }, + "state": { + "1": "state1" + }, + "shardId": "shard123456789", + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/ExampleTableWithStream/stream/2015-06-27T00:48:05.899", + "isFinalInvokeForWindow": false, + "isWindowTerminatedEarly": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/iot_button_event.json b/aws-lambda-java-tests/src/test/resources/iot_button_event.json new file mode 100644 index 000000000..8dc82826b --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/iot_button_event.json @@ -0,0 +1,5 @@ +{ + "serialNumber": "G030JF055364XVRB", + "clickType": "SINGLE", + "batteryVoltage": "2000mV" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json new file mode 100644 index 000000000..d9f682e5f --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kafka_event_roundtrip.json @@ -0,0 +1,22 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-east-1:123456789012:cluster/vpc-3432434/4834-3547-3455-9872-7929", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "mytopic-01": [ + { + "topic": "mytopic", + "partition": 0, + "offset": 15, + "timestamp": 1596480920837, + "timestampType": "CREATE_TIME", + "value": "SGVsbG8gZnJvbSBLYWZrYSAhIQ==", + "headers": [ + { + "headerKey": "aGVhZGVyVmFsdWU=" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json new file mode 100644 index 000000000..8c6cfe514 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_firehose_input_preprocessing.json @@ -0,0 +1,14 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "streamArn": "arn:aws:firehose:us-east-1:123456789012:deliverystream/my-stream", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "kinesisFirehoseRecordMetadata": { + "approximateArrivalTimestamp": 1583348638390 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json new file mode 100644 index 000000000..f5be190ec --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_input_preprocessing_response.json @@ -0,0 +1,9 @@ +{ + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "result": "Ok", + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json new file mode 100644 index 000000000..573b6baba --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery.json @@ -0,0 +1,13 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "lambdaDeliveryRecordMetadata": { + "retryHint": 0 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json new file mode 100644 index 000000000..56ddaa194 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_output_delivery_response.json @@ -0,0 +1,8 @@ +{ + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "result": "Ok" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json new file mode 100644 index 000000000..4ae8f3705 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_analytics_streams_input_preprocessing.json @@ -0,0 +1,17 @@ +{ + "invocationId": "invocationIdExample", + "applicationArn": "arn:aws:kinesisanalytics:us-east-1:123456789012:application/my-app", + "streamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/my-stream", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "kinesisStreamRecordMetadata": { + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "partitionKey": "partKey", + "shardId": "shardId-000000000000", + "approximateArrivalTimestamp": 1583348638390 + }, + "data": "SGVsbG8gV29ybGQ=" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json new file mode 100644 index 000000000..e2081ef2b --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_event_roundtrip.json @@ -0,0 +1,21 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "partitionKey-03", + "kinesisSchemaVersion": "1.0", + "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=", + "sequenceNumber": "49545115243490985018280067714973144582180062593244200961", + "approximateArrivalTimestamp": 1.4285376E9, + "encryptionType": "NONE" + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "eu-central-1" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json new file mode 100644 index 000000000..2d6283c58 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/kinesis/kinesis_time_window_event.json @@ -0,0 +1,32 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "1", + "sequenceNumber": "49590338271490256608559692538361571095921575989136588898", + "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0Lg==", + "approximateArrivalTimestamp": 1.607497475E9 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588898", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" + } + ], + "window": { + "start": "2020-12-09T07:04:00Z", + "end": "2020-12-09T07:06:00Z" + }, + "state": { + "1": "282", + "2": "715" + }, + "shardId": "shardId-000000000006", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream", + "isFinalInvokeForWindow": false, + "isWindowTerminatedEarly": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json new file mode 100644 index 000000000..be065eb3f --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/lex_event_roundtrip.json @@ -0,0 +1,24 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": { + "key": "value" + }, + "bot": { + "name": "BookTrip", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "BookHotel", + "slots": { + "Location": "Chicago", + "CheckInDate": "2030-11-08", + "Nights": "4", + "RoomType": "queen" + }, + "confirmationStatus": "None" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/mq_event.json b/aws-lambda-java-tests/src/test/resources/mq_event.json index 6505a22d4..8b12af72e 100644 --- a/aws-lambda-java-tests/src/test/resources/mq_event.json +++ b/aws-lambda-java-tests/src/test/resources/mq_event.json @@ -5,15 +5,17 @@ { "messageID": "ID:b-9bcfa592-423a-4942-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", "messageType": "jms/text-message", - "data": "QUJDOkFBQUE=", - "connectionId": "myJMSCoID", + "timestamp": 1598827811958, + "deliveryMode": 0, "redelivered": false, + "expiration": 0, + "priority": 0, + "data": "QUJDOkFBQUE=", + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, "destination": { "physicalname": "testQueue" }, - "timestamp": 1598827811958, - "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959, "properties": { "testKey": "testValue" } @@ -21,15 +23,17 @@ { "messageID": "ID:b-8bcfa572-428a-4642-879d-eb284b418fc8-1.mq.us-west-2.amazonaws.com-37557-1234520418293-4:1:1:1:1", "messageType": "jms/bytes-message", + "timestamp": 1598827811958, + "deliveryMode": 0, + "redelivered": false, + "expiration": 0, + "priority": 0, "data": "3DTOOW7crj51prgVLQaGQ82S48k=", - "connectionId": "myJMSCoID1", - "persistent": false, + "brokerInTime": 1598827811958, + "brokerOutTime": 1598827811959, "destination": { "physicalname": "testQueue" }, - "timestamp": 1598827811958, - "brokerInTime": 1598827811958, - "brokerOutTime": 1598827811959, "properties": { "testKey": "testValue" } diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json index 6b839912d..140908250 100644 --- a/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event.json @@ -15,4 +15,4 @@ "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" } ] -} +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json new file mode 100644 index 000000000..81b0a9c81 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/msk_firehose_event_roundtrip.json @@ -0,0 +1,18 @@ +{ + "invocationId": "12345621-4787-0000-a418-36e56Example", + "sourceMSKArn": "arn:aws:kafka:EXAMPLE", + "deliveryStreamArn": "arn:aws:firehose:EXAMPLE", + "region": "us-east-1", + "records": [ + { + "recordId": "00000000000000000000000000000000000000000000000000000000000000", + "approximateArrivalTimestamp": 1716369573887, + "mskRecordMetadata": { + "offset": "0", + "partitionId": "1", + "approximateArrivalTimestamp": "1716369573887" + }, + "kafkaRecordValue": "eyJOYW1lIjoiSGVsbG8gV29ybGQifQ==" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/partial_pojo.json b/aws-lambda-java-tests/src/test/resources/partial_pojo.json new file mode 100644 index 000000000..398218039 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/partial_pojo.json @@ -0,0 +1,4 @@ +{ + "name": "test", + "unknownField": "this will be dropped" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json new file mode 100644 index 000000000..44edf2f0a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/rabbitmq_event_roundtrip.json @@ -0,0 +1,51 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:112556298976:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "test::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": 60000, + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "eyJ0aW1lb3V0IjowLCJkYXRhIjoiQ1pybWYwR3c4T3Y0YnFMUXhENEUifQ==" + } + ] + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/alb_response.json b/aws-lambda-java-tests/src/test/resources/response/alb_response.json new file mode 100644 index 000000000..355eb193a --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/alb_response.json @@ -0,0 +1,15 @@ +{ + "statusCode": 200, + "statusDescription": "200 OK", + "headers": { + "Content-Type": "text/html" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1", + "cookie2=value2" + ] + }, + "body": "Hello", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json new file mode 100644 index 000000000..640ccdc5c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_proxy_response.json @@ -0,0 +1,15 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json", + "X-Custom-Header": "custom-value" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1", + "cookie2=value2" + ] + }, + "body": "{\"message\":\"Hello from Lambda\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json new file mode 100644 index 000000000..c39236650 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_http_response.json @@ -0,0 +1,16 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "Set-Cookie": [ + "cookie1=value1" + ] + }, + "cookies": [ + "session=abc123; Secure; HttpOnly" + ], + "body": "{\"message\":\"OK\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json new file mode 100644 index 000000000..08392e890 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/apigw_v2_websocket_response.json @@ -0,0 +1,14 @@ +{ + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "X-Custom": [ + "val1", + "val2" + ] + }, + "body": "{\"action\":\"sendmessage\",\"data\":\"hello\"}", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json new file mode 100644 index 000000000..9ac497624 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/msk_firehose_response.json @@ -0,0 +1,9 @@ +{ + "records": [ + { + "recordId": "record-1", + "result": "Ok", + "kafkaRecordValue": "dHJhbnNmb3JtZWQgZGF0YQ==" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json new file mode 100644 index 000000000..e63439d84 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/s3_batch_response.json @@ -0,0 +1,12 @@ +{ + "invocationSchemaVersion": "1.0", + "treatMissingKeysAs": "PermanentFailure", + "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo", + "results": [ + { + "taskId": "dGFza2lkZ29lc2hlcmUK", + "resultCode": "Succeeded", + "resultString": "Successfully processed" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json new file mode 100644 index 000000000..5f23b6405 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/simple_iam_policy_response.json @@ -0,0 +1,7 @@ +{ + "isAuthorized": true, + "context": { + "userId": "user-123", + "scope": "read:all" + } +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json new file mode 100644 index 000000000..5ef2de697 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/response/sqs_batch_response.json @@ -0,0 +1,7 @@ +{ + "batchItemFailures": [ + { + "itemIdentifier": "059f36b4-87a3-44ab-83d2-661975830a7d" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/s3_batch_event.json b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json new file mode 100644 index 000000000..a70af0fdd --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/s3_batch_event.json @@ -0,0 +1,15 @@ +{ + "invocationSchemaVersion": "1.0", + "invocationId": "YXNkbGZqYWRmaiBhc2RmdW9hZHNmZGpmaGFzbGtkaGZza2RmaAo", + "job": { + "id": "f3cc4f60-61f6-4a2b-8a21-d07600c373ce" + }, + "tasks": [ + { + "taskId": "dGFza2lkZ29lc2hlcmUK", + "s3Key": "customerImage1.jpg", + "s3VersionId": "1", + "s3BucketArn": "arn:aws:s3:::amzn-s3-demo-bucket" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/s3_event.json b/aws-lambda-java-tests/src/test/resources/s3_event.json index 89e0bd312..73f59d072 100644 --- a/aws-lambda-java-tests/src/test/resources/s3_event.json +++ b/aws-lambda-java-tests/src/test/resources/s3_event.json @@ -28,8 +28,10 @@ }, "object": { "key": "test/key", + "urlDecodedKey": "test/key", "size": 1024, "eTag": "0123456789abcdef0123456789abcdef", + "versionId": "", "sequencer": "0A1B2C3D4E5F678901" } } diff --git a/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json new file mode 100644 index 000000000..db996e71c --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/s3_object_lambda_event.json @@ -0,0 +1,29 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=snip", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "AssumedRole", + "principalId": "principalId", + "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/time_window_event_response.json b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json new file mode 100644 index 000000000..3c77b3784 --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/time_window_event_response.json @@ -0,0 +1,10 @@ +{ + "state": { + "totalAmount": "500" + }, + "batchItemFailures": [ + { + "itemIdentifier": "49590338271490256608559692538361571095921575989136588898" + } + ] +} \ No newline at end of file diff --git a/aws-lambda-java-tests/src/test/resources/unstable_pojo.json b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json new file mode 100644 index 000000000..19db9a1cc --- /dev/null +++ b/aws-lambda-java-tests/src/test/resources/unstable_pojo.json @@ -0,0 +1,3 @@ +{ + "name": "test" +} \ No newline at end of file From 3d9661ee89c92f10a52352ad41a5fe3343ddd832 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:32:43 +0100 Subject: [PATCH 113/173] Bump actions/setup-java from 4 to 5 (#561) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Davide Melfi --- .github/workflows/aws-lambda-java-core.yml | 2 +- .github/workflows/aws-lambda-java-events-sdk-transformer.yml | 2 +- .github/workflows/aws-lambda-java-events.yml | 2 +- .github/workflows/aws-lambda-java-log4j2.yml | 2 +- .github/workflows/aws-lambda-java-profiler.yml | 2 +- .github/workflows/aws-lambda-java-serialization.yml | 2 +- .github/workflows/aws-lambda-java-tests.yml | 2 +- .github/workflows/runtime-interface-client_merge_to_main.yml | 2 +- .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 4 ++-- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/aws-lambda-java-core.yml b/.github/workflows/aws-lambda-java-core.yml index 373acf757..3e4364672 100644 --- a/.github/workflows/aws-lambda-java-core.yml +++ b/.github/workflows/aws-lambda-java-core.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml index 713f97211..144d52f86 100644 --- a/.github/workflows/aws-lambda-java-events-sdk-transformer.yml +++ b/.github/workflows/aws-lambda-java-events-sdk-transformer.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-events.yml b/.github/workflows/aws-lambda-java-events.yml index 0a8725339..18be63cf9 100644 --- a/.github/workflows/aws-lambda-java-events.yml +++ b/.github/workflows/aws-lambda-java-events.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-log4j2.yml b/.github/workflows/aws-lambda-java-log4j2.yml index 315678339..945a1cb30 100644 --- a/.github/workflows/aws-lambda-java-log4j2.yml +++ b/.github/workflows/aws-lambda-java-log4j2.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 1007e7124..485d93110 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index 78d23c37b..ccc805be7 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/aws-lambda-java-tests.yml b/.github/workflows/aws-lambda-java-tests.yml index a52181dff..324c44514 100644 --- a/.github/workflows/aws-lambda-java-tests.yml +++ b/.github/workflows/aws-lambda-java-tests.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index da2389d73..f66310755 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 59b1c8aed..645f1069c 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto @@ -51,7 +51,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index a76c3fb25..ef961c3e0 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 8 distribution: corretto @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v6 # Set up both Java 8 and 21 - name: Set up Java 8 and 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: | 8 From 3239ddac451d3678a8f248143560809645321ed7 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Tue, 31 Mar 2026 10:01:20 +0100 Subject: [PATCH 114/173] Re-apply Jackson 2.18.6 upgrade with round-trip test coverage (#599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 🧪 add roundtrip serialization test utility * test: fix false positives epoch format errors, added comment about this in the serialization package. * test: fixed false positves DateTime differences * test: fixing error in lex event fixture * test: fixing connect event * test: fixing api gateway proxy event false negative * test: fixing cloudfront and s3 event false negatives * chore: adding mise to gitignore * test: fix MSKFirehose, LexEvent, RabbitMQ, APIGatewayV2Auth and ActiveMQ serialization test fixtures * test: Add round-trip fixtures for 4 registered events Add JSON test fixtures and round-trip test cases for CognitoEvent, DynamodbTimeWindowEvent, IoTButtonEvent, and KinesisTimeWindowEvent. These were the only events registered in LambdaEventSerializers SUPPORTED_EVENTS that lacked test fixtures. Fixtures based on official AWS Lambda documentation examples. Time window event fixtures use round-trip-safe date formats to avoid coercion issues. Coverage: 32 passing + 2 known failures (34 total, up from 30). * test: Add round-trip fixtures for 21 unregistered events Add UnregisteredEventSerializationRoundTripTest covering events not in LambdaEventSerializers.SUPPORTED_EVENTS: 10 Cognito UserPool triggers, 5 Kinesis Analytics events, 2 API Gateway V2 WebSocket, 2 AppSync, 1 S3 Batch, and 1 TimeWindow response. S3ObjectLambdaEvent is a known failure (Lombok xAmzRequestId naming issue). Split SerializationRoundTripTest into registered (34 tests) and unregistered (22 tests) for clarity. Total: 56 tests, 53 passing, 3 known failures. * test: Add round-trip tests for 11 response event types Add ResponseEventSerializationRoundTripTest covering all 11 response event types in aws-lambda-java-events. 9 pass cleanly, 2 are known failures (IamPolicyResponse, IamPolicyResponseV1 — getPolicyDocument() returns Map instead of the PolicyDocument POJO, making round-trip impossible by design since these are serialize-only types). Also update SerializationRoundTripTest comment for APIGatewayV2CustomAuthorizerEvent to clarify the date format change is a lossy transformation, not a bug. Total: 69 tests (34 registered + 22 unregistered + 11 response + 2 LambdaEventAssertTest), all green. Coverage now 66/68 event classes (97%). * chore: fixed comment * test: including IAM Policy Reponse roundtrip test * test: add test for JsonNodeUtils * chore: remove unwanted file on origin * docs: add tests 1.1.2 changelog entry * chore: remove entry in changelog * fix: 🔧 updating again jacjson 2.15.4 -> 2.18.6 * fix: fixing an error in github actions that was causing false positive errors when running aws-lambda-java-serialization * docs: update changelogs * chore: releasing aws-lambda-tests * chore: add space --------- Co-authored-by: Davide Melfi --- .../aws-lambda-java-serialization.yml | 2 +- .../RELEASE.CHANGELOG.md | 7 + aws-lambda-java-serialization/pom.xml | 4 +- .../events/LambdaEventSerializers.java | 285 +++++++++--------- aws-lambda-java-tests/RELEASE.CHANGELOG.md | 6 + aws-lambda-java-tests/pom.xml | 14 +- .../runtime/tests/LambdaEventAssert.java | 7 +- 7 files changed, 164 insertions(+), 161 deletions(-) diff --git a/.github/workflows/aws-lambda-java-serialization.yml b/.github/workflows/aws-lambda-java-serialization.yml index ccc805be7..f52c96fed 100644 --- a/.github/workflows/aws-lambda-java-serialization.yml +++ b/.github/workflows/aws-lambda-java-serialization.yml @@ -40,7 +40,7 @@ jobs: # Package and install target module - name: Package serialization with Maven - run: mvn -B package install --file aws-lambda-java-serialization/pom.xml + run: mvn -B install --file aws-lambda-java-serialization/pom.xml # Run tests - name: Run tests from aws-lambda-java-tests diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index 4a0e8ca2a..d68d7b1fe 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,7 +1,14 @@ +### March 26, 2026 +`1.4.0`: +- Update `jackson-databind` dependency from 2.15.4 to 2.18.6 +- Replace deprecated `PropertyNamingStrategy.PascalCaseStrategy` with `PropertyNamingStrategies.UpperCamelCaseStrategy` +- The regression reported in 1.3.1 was a false positive caused by a CI workflow bug (`mvn package install` running the shade plugin twice, corrupting the artifact). Fixed by using `mvn install` instead. + ### March 19, 2026 `1.3.1`: - Revert `jackson-databind` dependency from 2.18.6 to 2.15.4 - Revert `PropertyNamingStrategies.UpperCamelCaseStrategy` to `PropertyNamingStrategy.PascalCaseStrategy` +- Note: reverted due to a suspected regression in Joda DateTime deserialization; later confirmed to be a false positive (see 1.4.0) ### March 11, 2026 `1.3.0`: diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index a71eb1f8d..d412fd765 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.3.1 + 1.4.0 jar AWS Lambda Java Runtime Serialization @@ -32,7 +32,7 @@ 1.8 1.8 com.amazonaws.lambda.thirdparty - 2.15.4 + 2.18.6 2.10.1 20231013 7.3.2 diff --git a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java index 3f01d99b1..533bdcd49 100644 --- a/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java +++ b/aws-lambda-java-serialization/src/main/java/com/amazonaws/services/lambda/runtime/serialization/events/LambdaEventSerializers.java @@ -19,6 +19,7 @@ import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil; import com.amazonaws.services.lambda.runtime.serialization.util.SerializeUtil; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateModule; import com.amazonaws.services.lambda.runtime.serialization.events.modules.DateTimeModule; @@ -86,156 +87,146 @@ public class LambdaEventSerializers { "com.amazonaws.services.lambda.runtime.events.SQSEvent") .collect(Collectors.toList()); - /** - * list of events incompatible with Jackson, with serializers explicitly defined - * Classes are incompatible with Jackson for any of the following reasons: - * 1. different constructor/setter types from getter types - * 2. various bugs within Jackson - */ - private static final Map SERIALIZER_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", - new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", - new S3EventSerializer<>()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", - new S3EventSerializer<>())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * list of events incompatible with Jackson, with serializers explicitly defined + * Classes are incompatible with Jackson for any of the following reasons: + * 1. different constructor/setter types from getter types + * 2. various bugs within Jackson + */ + private static final Map SERIALIZER_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.s3.event.S3EventNotification", new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification", new S3EventSerializer<>()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.S3Event", new S3EventSerializer<>())) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * Maps supported event classes to mixin classes with Jackson annotations. - * Jackson annotations are not loaded through the ClassLoader so if a Java field - * is serialized or deserialized from a - * json field that does not match the Jave field name, then a Mixin is required. - */ - @SuppressWarnings("rawtypes") - private static final Map MIXIN_MAP = Stream.of( - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", - CloudFormationCustomResourceEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", - CloudFrontEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", - CloudWatchLogsEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - CodeCommitEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", - CodeCommitEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - ConnectEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", - ConnectEventMixin.DetailsMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", - ConnectEventMixin.ContactDataMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", - ConnectEventMixin.CustomerEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - ConnectEventMixin.QueueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", - ConnectEventMixin.SystemEndpointMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - DynamodbEventMixin.class), - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - DynamodbEventMixin.DynamodbStreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - DynamodbEventMixin.StreamRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - DynamodbEventMixin.AttributeValueMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - DynamodbTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - KinesisEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", - KinesisEventMixin.RecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", - KinesisTimeWindowEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", - ScheduledEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", - SecretsManagerRotationEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - SNSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", - SNSEventMixin.SNSRecordMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - SQSEventMixin.class), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", - SQSEventMixin.SQSMessageMixin.class)) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * Maps supported event classes to mixin classes with Jackson annotations. + * Jackson annotations are not loaded through the ClassLoader so if a Java field is serialized or deserialized from a + * json field that does not match the Jave field name, then a Mixin is required. + */ + @SuppressWarnings("rawtypes") + private static final Map MIXIN_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent", + CloudFormationCustomResourceEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudFrontEvent", + CloudFrontEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CloudWatchLogsEvent", + CloudWatchLogsEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + CodeCommitEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record", + CodeCommitEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + ConnectEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details", + ConnectEventMixin.DetailsMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData", + ConnectEventMixin.ContactDataMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint", + ConnectEventMixin.CustomerEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", ConnectEventMixin.QueueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint", + ConnectEventMixin.SystemEndpointMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + DynamodbEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + DynamodbEventMixin.DynamodbStreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + DynamodbEventMixin.StreamRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.dynamodbv2.model.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + DynamodbEventMixin.AttributeValueMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + DynamodbTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + KinesisEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record", + KinesisEventMixin.RecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisTimeWindowEvent", + KinesisTimeWindowEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ScheduledEvent", + ScheduledEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SecretsManagerRotationEvent", + SecretsManagerRotationEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + SNSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord", + SNSEventMixin.SNSRecordMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + SQSEventMixin.class), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage", + SQSEventMixin.SQSMessageMixin.class)) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * If mixins are required for inner classes of an event, then those nested - * classes must be specified here. - */ - @SuppressWarnings("rawtypes") - private static final Map> NESTED_CLASS_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>( - "com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", - Arrays.asList( - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", - "com.amazonaws.services.dynamodbv2.model.AttributeValue"), - new AlternateNestedClass( - "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", - "com.amazonaws.services.dynamodbv2.model.StreamRecord"), - new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", - Arrays.asList( - new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If mixins are required for inner classes of an event, then those nested classes must be specified here. + */ + @SuppressWarnings("rawtypes") + private static final Map> NESTED_CLASS_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CodeCommitEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.CognitoEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.CognitoEvent$DatasetRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Details"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$ContactData"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$CustomerEndpoint"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.ConnectEvent$SystemEndpoint"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.DynamodbTimeWindowEvent", + Arrays.asList( + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.AttributeValue", + "com.amazonaws.services.dynamodbv2.model.AttributeValue"), + new AlternateNestedClass( + "com.amazonaws.services.lambda.runtime.events.models.dynamodb.StreamRecord", + "com.amazonaws.services.dynamodbv2.model.StreamRecord"), + new NestedClass("com.amazonaws.services.lambda.runtime.events.DynamodbEvent$DynamodbStreamRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.KinesisEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.KinesisEvent$Record"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SNSEvent$SNSRecord"))), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SQSEvent", + Arrays.asList( + new NestedClass("com.amazonaws.services.lambda.runtime.events.SQSEvent$SQSMessage")))) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); - /** - * If event requires a naming strategy. For example, when someone names the - * getter method getSNS and the setter - * method setSns, for some magical reasons, using both mixins and a naming - * strategy works - */ - private static final Map NAMING_STRATEGY_MAP = Stream.of( - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", - new PropertyNamingStrategy.PascalCaseStrategy()), - new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", - new PropertyNamingStrategy.PascalCaseStrategy())) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); + /** + * If event requires a naming strategy. For example, when someone names the getter method getSNS and the setter + * method setSns, for some magical reasons, using both mixins and a naming strategy works + */ + private static final Map NAMING_STRATEGY_MAP = Stream.of( + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.SNSEvent", + new PropertyNamingStrategies.UpperCamelCaseStrategy()), + new SimpleEntry<>("com.amazonaws.services.lambda.runtime.events.ConnectEvent$Queue", + new PropertyNamingStrategies.UpperCamelCaseStrategy()) + ) + .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)); /** * Returns whether the class name is a Lambda supported event model. diff --git a/aws-lambda-java-tests/RELEASE.CHANGELOG.md b/aws-lambda-java-tests/RELEASE.CHANGELOG.md index 76965b8fd..0b4bd2510 100644 --- a/aws-lambda-java-tests/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-tests/RELEASE.CHANGELOG.md @@ -1,3 +1,9 @@ +### March 27, 2026 +`1.1.3`: +- Add serialization round-trip tests covering 66 event classes +- Bumped `aws-lambda-java-serialization` to version `1.4.0` (Jackson `2.15.x` → `2.18.6`) +- Bumped `aws-lambda-java-events` to version `3.16.1` + ### August 26, 2021 `1.1.1`: - Bumped `aws-lambda-java-events` to version `3.11.0` diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 69a112cba..e63e529a2 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.2 + 1.1.3 jar AWS Lambda Java Tests @@ -40,18 +40,22 @@ --> 5.9.2 0.8.7 + 1.4.0 + 3.16.1 + 3.18.0 + 3.27.7 com.amazonaws aws-lambda-java-serialization - 1.2.0 + ${aws-lambda-java-serialization.version} com.amazonaws aws-lambda-java-events - 3.16.1 + ${aws-lambda-java-events.version} org.junit.jupiter @@ -71,13 +75,13 @@ org.apache.commons commons-lang3 - 3.18.0 + ${commons-lang3.version} org.assertj assertj-core - 3.27.7 + ${assertj-core.version} test diff --git a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java index e189b5e2b..f8d7e106d 100644 --- a/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java +++ b/aws-lambda-java-tests/src/main/java/com/amazonaws/services/lambda/runtime/tests/LambdaEventAssert.java @@ -25,13 +25,8 @@ * {@link AssertionError}. *

    * - *

    - * This class is intentionally package-private to support updates to - * the aws-lambda-java-events and aws-lambda-java-serialization packages. - * Consider making it public if there's a real request for it. - *

    */ -class LambdaEventAssert { +public class LambdaEventAssert { private static final ObjectMapper MAPPER = new ObjectMapper(); From 7230727de124386f9e9e49c04f2586a384b1bb02 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 31 Mar 2026 13:40:49 -0400 Subject: [PATCH 115/173] fix: pin GitHub Actions (#601) --- .github/workflows/repo-sync.yml | 4 ++-- .../runtime-interface-client_merge_to_main.yml | 12 ++++++------ .github/workflows/runtime-interface-client_pr.yml | 4 ++-- .github/workflows/samples.yml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/repo-sync.yml b/.github/workflows/repo-sync.yml index 6a918fde3..4934754d8 100644 --- a/.github/workflows/repo-sync.yml +++ b/.github/workflows/repo-sync.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 if: ${{ env.IS_CONFIGURED == 'true' }} - - uses: repo-sync/github-sync@v2 + - uses: repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88 # v2.3.0 name: Sync repo to branch if: ${{ env.IS_CONFIGURED == 'true' }} with: @@ -30,7 +30,7 @@ jobs: source_branch: main destination_branch: ${{ secrets.INTERMEDIATE_BRANCH }} github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: repo-sync/pull-request@v2 + - uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 # v2.12.1 name: Create pull request if: ${{ env.IS_CONFIGURED == 'true' }} with: diff --git a/.github/workflows/runtime-interface-client_merge_to_main.yml b/.github/workflows/runtime-interface-client_merge_to_main.yml index f66310755..d0d479111 100644 --- a/.github/workflows/runtime-interface-client_merge_to_main.yml +++ b/.github/workflows/runtime-interface-client_merge_to_main.yml @@ -28,20 +28,20 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto cache: maven - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: install: true @@ -62,7 +62,7 @@ jobs: if: env.ENABLE_SNAPSHOT != null env: ENABLE_SNAPSHOT: ${{ secrets.ENABLE_SNAPSHOT }} - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 with: aws-region: ${{ secrets.AWS_REGION }} role-to-assume: ${{ secrets.AWS_ROLE }} @@ -91,6 +91,6 @@ jobs: - name: Upload coverage to Codecov if: env.CODECOV_TOKEN != null - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index 645f1069c..e0522005b 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -58,7 +58,7 @@ jobs: cache: maven - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -90,6 +90,6 @@ jobs: - name: Upload coverage to Codecov if: env.CODECOV_TOKEN != null - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index ef961c3e0..68e25827d 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -73,7 +73,7 @@ jobs: # Build custom-serialization samples - name: install sam - uses: aws-actions/setup-sam@v2 + uses: aws-actions/setup-sam@d78e1a4a9656d3b223e59b80676a797f20093133 # v2 - name: test fastJson run: cd samples/custom-serialization/fastJson && sam build && sam local invoke -e events/event.json | grep 200 - name: test gson From c4dcbab4ffeda26ec9dff997d6ef02a9ee2ab013 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Wed, 1 Apr 2026 08:43:15 -0400 Subject: [PATCH 116/173] fix: pinning actions (#602) --- .github/workflows/aws-lambda-java-profiler.yml | 8 ++++---- .github/workflows/runtime-interface-client_pr.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/aws-lambda-java-profiler.yml b/.github/workflows/aws-lambda-java-profiler.yml index 485d93110..a098bfd14 100644 --- a/.github/workflows/aws-lambda-java-profiler.yml +++ b/.github/workflows/aws-lambda-java-profiler.yml @@ -22,17 +22,17 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 21 distribution: corretto cache: maven - name: Issue AWS credentials - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 with: aws-region: ${{ secrets.AWS_REGION_PROFILER_EXTENSION_INTEGRATION_TEST }} role-to-assume: ${{ secrets.AWS_ROLE_PROFILER_EXTENSION_INTEGRATION_TEST }} @@ -68,7 +68,7 @@ jobs: run: ./integration_tests/download_from_s3.sh - name: Upload profiles - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: profiles path: /tmp/s3-artifacts diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index e0522005b..a0d8c6cc8 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -22,10 +22,10 @@ jobs: smoke-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto @@ -48,10 +48,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 1.8 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: 8 distribution: corretto @@ -61,7 +61,7 @@ jobs: uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: install: true @@ -83,7 +83,7 @@ jobs: IS_JAVA_8: true - name: Save the built jar - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: aws-lambda-java-runtime-interface-client path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar From 04c165c61d4d916171295443c0e75268c9a2a9e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 12:02:38 +0100 Subject: [PATCH 117/173] chore(deps): bump org.apache.logging.log4j:log4j-core (#603) Bumps org.apache.logging.log4j:log4j-core from 2.25.3 to 2.25.4. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- aws-lambda-java-log4j2/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 0124598a0..7c45c10bb 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -34,7 +34,7 @@ 1.8 1.8 - 2.25.3 + 2.25.4 From 319b771fb20729d649b95a448c69b2777a063b07 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Wed, 13 May 2026 18:08:29 +0100 Subject: [PATCH 118/173] chore(release): release aws-lambda-java-log4j2 ver 1.6.3 (#604) --- aws-lambda-java-log4j2/README.md | 10 +++++----- aws-lambda-java-log4j2/RELEASE.CHANGELOG.md | 8 ++++++++ aws-lambda-java-log4j2/pom.xml | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/aws-lambda-java-log4j2/README.md b/aws-lambda-java-log4j2/README.md index f13121750..b626226ad 100644 --- a/aws-lambda-java-log4j2/README.md +++ b/aws-lambda-java-log4j2/README.md @@ -10,22 +10,22 @@ Example for Maven pom.xml com.amazonaws aws-lambda-java-log4j2 - 1.6.0 + 1.6.3 org.apache.logging.log4j log4j-core - 2.17.1 + 2.25.4 org.apache.logging.log4j log4j-api - 2.17.1 + 2.25.4 org.apache.logging.log4j log4j-layout-template-json - 2.17.1 + 2.25.4 .... @@ -73,7 +73,7 @@ If you are using the [John Rengelman](https://github.com/johnrengelman/shadow) G dependencies{ ... - implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.0' + implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.3' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion } diff --git a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md index 49535d388..44528432d 100644 --- a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md @@ -1,3 +1,11 @@ +### May 13, 2026 +`1.6.3`: +- Updated `log4j-core` and `log4j-api` dependencies to `2.25.4` + +### February 2026 +`1.6.2`: +- Updated `log4j-core` and `log4j-api` dependencies to `2.25.3` + ### October 24, 2023 `1.6.0`: - Log level and log format support diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 7c45c10bb..570f8ce34 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.6.1 + 1.6.3 jar AWS Lambda Java Log4j 2.x Libraries From 0360241778aaf4f4deca83efa6ed6463867f324c Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Fri, 15 May 2026 14:23:52 +0100 Subject: [PATCH 119/173] chore(release): release aws-lambda-runtime-interface-client ver 2.11.0 (#605) --- aws-lambda-java-runtime-interface-client/README.md | 4 ++-- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-runtime-interface-client/pom.xml | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index c448e7a89..b72a6238c 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -70,7 +70,7 @@ pom.xml com.amazonaws aws-lambda-java-runtime-interface-client - 2.10.1 + 2.11.0 @@ -203,7 +203,7 @@ platform-specific JAR by setting the ``. com.amazonaws aws-lambda-java-runtime-interface-client - 2.10.1 + 2.11.0 linux-x86_64 ``` diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 94a8a8057..883bd56da 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 13, 2026 +`2.11.0` +- Update aws-lambda-java-serialization dependency to 1.4.0 + ### March 19, 2026 `2.10.1` - Revert aws-lambda-java-serialization dependency to 1.2.0 diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a09fd3df7..47e79a623 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.10.1 + 2.11.0 jar AWS Lambda Java Runtime Interface Client @@ -67,7 +67,7 @@ com.amazonaws aws-lambda-java-serialization - 1.2.0 + 1.4.0 software.amazon.awssdk From 93f6d540705e5b2e24d9baf2f73507f00e2d7ff8 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Tue, 19 May 2026 19:13:38 +0100 Subject: [PATCH 120/173] Update Log4J version to fix regression (#613) * chore: update version * chore: updating release note and changelog --- aws-lambda-java-log4j2/README.md | 4 ++-- aws-lambda-java-log4j2/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-log4j2/pom.xml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/aws-lambda-java-log4j2/README.md b/aws-lambda-java-log4j2/README.md index b626226ad..480df21df 100644 --- a/aws-lambda-java-log4j2/README.md +++ b/aws-lambda-java-log4j2/README.md @@ -10,7 +10,7 @@ Example for Maven pom.xml com.amazonaws aws-lambda-java-log4j2 - 1.6.3 + 1.6.4 org.apache.logging.log4j @@ -73,7 +73,7 @@ If you are using the [John Rengelman](https://github.com/johnrengelman/shadow) G dependencies{ ... - implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.3' + implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.4' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion } diff --git a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md index 44528432d..5f43862a3 100644 --- a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 19, 2026 +`1.6.4`: +- Fix regression in `1.6.3` + ### May 13, 2026 `1.6.3`: - Updated `log4j-core` and `log4j-api` dependencies to `2.25.4` diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 570f8ce34..6f142d57c 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.6.3 + 1.6.4 jar AWS Lambda Java Log4j 2.x Libraries From 8adb3d76a813964e1ae2ca3aeded62b0407ed80f Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Wed, 20 May 2026 15:35:50 +0100 Subject: [PATCH 121/173] chore (update): update version of serialization to 1.4.1 (#614) --- aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md | 2 +- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- aws-lambda-java-serialization/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 883bd56da..2391045fc 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,6 +1,6 @@ ### May 13, 2026 `2.11.0` -- Update aws-lambda-java-serialization dependency to 1.4.0 +- Update aws-lambda-java-serialization dependency to 1.4.1 ### March 19, 2026 `2.10.1` diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 47e79a623..2ba71c43c 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -67,7 +67,7 @@ com.amazonaws aws-lambda-java-serialization - 1.4.0 + 1.4.1 software.amazon.awssdk diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index d68d7b1fe..3bb977937 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### May 20, 2026 +`1.4.1`: +- Fix build issue + ### March 26, 2026 `1.4.0`: - Update `jackson-databind` dependency from 2.15.4 to 2.18.6 diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index d412fd765..60a3c67a9 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.4.0 + 1.4.1 jar AWS Lambda Java Runtime Serialization diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index e63e529a2..5e5f19b33 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -40,7 +40,7 @@ --> 5.9.2 0.8.7 - 1.4.0 + 1.4.1 3.16.1 3.18.0 3.27.7 From 1901fac6f7224288396cdc4b44bf0ae53a0f35a5 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Sat, 23 May 2026 08:20:20 +0100 Subject: [PATCH 122/173] build: Pin compilation to JDK 8 via Maven Toolchains --- aws-lambda-java-core/pom.xml | 26 +++++++++++++++++++ .../pom.xml | 21 +++++++++++++++ aws-lambda-java-events/pom.xml | 26 +++++++++++++++++++ .../pom.xml | 21 +++++++++++++++ aws-lambda-java-serialization/pom.xml | 21 +++++++++++++++ aws-lambda-java-tests/pom.xml | 21 +++++++++++++++ 6 files changed, 136 insertions(+) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..0a11aa517 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -36,6 +36,32 @@ 1.8 + + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + + + + dev diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6de599ef7..11054be12 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -79,6 +79,27 @@ + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + maven-surefire-plugin ${maven-surefire-plugin.version} diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index c8c40e0c7..1ac5d7981 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -42,6 +42,32 @@ 5.12.2 + + + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + + + + sonatype-nexus-staging diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 2ba71c43c..fa543df00 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -115,6 +115,27 @@ + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + maven-install-plugin org.apache.maven.plugins diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 60a3c67a9..503f3e76f 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -191,6 +191,27 @@ + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + org.apache.maven.plugins maven-shade-plugin diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 5e5f19b33..4a25586ce 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -245,6 +245,27 @@ + + org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + org.apache.maven.plugins maven-compiler-plugin From 2714c2c38c11fd198f8e3ce75feae7d3627a5933 Mon Sep 17 00:00:00 2001 From: Ryan Schmitt Date: Sat, 23 May 2026 06:44:21 -0700 Subject: [PATCH 123/173] test(log4j2): Add initial test suite covering plugin registration (#615) This package previously had no tests and no JUnit dependency. Add JUnit Jupiter, the surefire plugin, and an initial test class that exercises the full plugin resolution path end-to-end: a log4j2.xml on the test classpath, a real LogManager-issued logger, and stdout capture (LambdaAppender writes through LambdaRuntime.getLogger() to System.out). The TEXT test routes the root logger through LambdaTextFormat with a deterministic PatternLayout and asserts each level appears. The JSON test adds a second LambdaAppender with format="JSON" backed by JsonTemplateLayout + LambdaLayout.json, attached via additivity=false to a "json-test" logger, and asserts the messages show up JSON-encoded. Add log4j-layout-template-json at test scope so the JSON path can resolve at test time. Users are still expected to bring their own copy at runtime (peer-dependency model, like PatternLayout); the published artifact's dependency surface is unchanged. The tests succeed on Java 8 and fail on Java 25, because annotation processors are not run by default on Java 25, Log4j2Plugins.dat is not generated for our plugins, and both tests surface the resulting CLASS_NOT_FOUND from the log4j status logger. --- aws-lambda-java-log4j2/pom.xml | 25 +++++- .../log4j2/LambdaAppenderPluginTest.java | 86 +++++++++++++++++++ .../src/test/resources/log4j2.xml | 25 ++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 aws-lambda-java-log4j2/src/test/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppenderPluginTest.java create mode 100644 aws-lambda-java-log4j2/src/test/resources/log4j2.xml diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 6f142d57c..469c2e1f0 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -35,6 +35,7 @@ 1.8 1.8 2.25.4 + 5.12.2 @@ -60,8 +61,30 @@ log4j-api ${log4j.version} + + org.apache.logging.log4j + log4j-layout-template-json + ${log4j.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + + dev @@ -146,4 +169,4 @@ - \ No newline at end of file + diff --git a/aws-lambda-java-log4j2/src/test/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppenderPluginTest.java b/aws-lambda-java-log4j2/src/test/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppenderPluginTest.java new file mode 100644 index 000000000..0bcd057a6 --- /dev/null +++ b/aws-lambda-java-log4j2/src/test/java/com/amazonaws/services/lambda/runtime/log4j2/LambdaAppenderPluginTest.java @@ -0,0 +1,86 @@ +/* Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ + +package com.amazonaws.services.lambda.runtime.log4j2; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LambdaAppenderPluginTest { + + private final PrintStream originalOut = System.out; + private ByteArrayOutputStream captured; + + @BeforeEach + void redirectStdout() throws UnsupportedEncodingException { + captured = new ByteArrayOutputStream(); + System.setOut(new PrintStream(captured, true, StandardCharsets.UTF_8.name())); + } + + @AfterEach + void restoreStdout() { + System.setOut(originalOut); + } + + @Test + void lambdaAppenderEmitsLogsAtVariousLevels() throws UnsupportedEncodingException { + Logger logger = LogManager.getLogger(LambdaAppenderPluginTest.class); + + logger.debug("debug-msg"); + logger.info("info-msg"); + logger.warn("warn-msg"); + logger.error("error-msg"); + + String output = captured.toString(StandardCharsets.UTF_8.name()); + + // The PatternLayout in src/test/resources/log4j2.xml is "%-5p %c{1} - %m%n", + // so each event should appear as " LambdaAppenderPluginTest - ". + assertTrue(output.contains("DEBUG LambdaAppenderPluginTest - debug-msg"), + "expected DEBUG line in output but got:\n" + output); + assertTrue(output.contains("INFO LambdaAppenderPluginTest - info-msg"), + "expected INFO line in output but got:\n" + output); + assertTrue(output.contains("WARN LambdaAppenderPluginTest - warn-msg"), + "expected WARN line in output but got:\n" + output); + assertTrue(output.contains("ERROR LambdaAppenderPluginTest - error-msg"), + "expected ERROR line in output but got:\n" + output); + + // Sanity check: log4j should not have fallen back to its default + // ConsoleAppender / status logger error message. + assertFalse(output.contains("ERROR StatusLogger"), + "log4j status logger reported an error, output was:\n" + output); + } + + @Test + void lambdaAppenderEmitsJsonForJsonFormatLogger() throws UnsupportedEncodingException { + // The "json-test" logger is configured in src/test/resources/log4j2.xml + // with additivity=false to a second LambdaAppender using format="JSON" + // and JsonTemplateLayout backed by LambdaLayout.json. + Logger logger = LogManager.getLogger("json-test"); + + logger.info("json-info-msg"); + logger.error("json-error-msg"); + + String output = captured.toString(StandardCharsets.UTF_8.name()); + + assertTrue(output.contains("json-info-msg"), + "expected json-info-msg in output but got:\n" + output); + assertTrue(output.contains("json-error-msg"), + "expected json-error-msg in output but got:\n" + output); + + // Output should look like JSON, not the text PatternLayout from the + // root logger — so it must contain JSON field punctuation around the + // message rather than the "INFO json-test - ..." text pattern. + assertTrue(output.contains("\"message\":\"json-info-msg\""), + "expected JSON-encoded message field but got:\n" + output); + } +} diff --git a/aws-lambda-java-log4j2/src/test/resources/log4j2.xml b/aws-lambda-java-log4j2/src/test/resources/log4j2.xml new file mode 100644 index 000000000..7b43094e2 --- /dev/null +++ b/aws-lambda-java-log4j2/src/test/resources/log4j2.xml @@ -0,0 +1,25 @@ + + + + + + + %-5p %c{1} - %m%n + + + + + + + + + + + + + + + + + + From 0abf6d7b52e6b7e4d4ed129ecb1b06188eaa14d6 Mon Sep 17 00:00:00 2001 From: Ryan Schmitt Date: Sat, 23 May 2026 06:55:56 -0700 Subject: [PATCH 124/173] build(log4j2): Pin compilation to JDK 8 via Maven Toolchains (#616) Release 1.6.3 was built on a JDK that does not run annotation processors by default, which silently dropped the Log4j2 plugin descriptor (META-INF/.../Log4j2Plugins.dat) for LambdaAppender, LambdaTextFormat, and LambdaJsonFormat. The published artifact was broken at runtime: log4j could not resolve , , or elements in user log4j2.xml configurations. Configure maven-toolchains-plugin to require a JDK 8 toolchain so javac comes from a JDK that runs annotation processors by default, regardless of which JVM Maven is invoked under. The version range [1.8,9) matches both "1.8" and "8". The existing GitHub Actions workflow at .github/workflows/aws-lambda-java-log4j2.yml uses actions/setup-java@v5 with java-version: 8 and distribution: corretto. setup-java@v5 auto-generates a ~/.m2/toolchains.xml entry with 8, which the [1.8,9) range matches, so no workflow changes are required. When no matching JDK 8 toolchain is available, the build now fails fast at the validate phase with a clear "Cannot find matching toolchain definitions" error instead of silently producing an artifact missing its plugin descriptor. Co-authored-by: Davide Melfi --- aws-lambda-java-log4j2/pom.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 469c2e1f0..a03d3d3b6 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -79,6 +79,26 @@ org.apache.maven.plugins + maven-toolchains-plugin + 3.2.0 + + + + + [1.8,9) + + + + + + + toolchain + + + + + maven-surefire-plugin 3.5.2 From cfefd3bb692597d1f5effa9c99f84021cd35f0e0 Mon Sep 17 00:00:00 2001 From: Jonathan Tuliani Date: Fri, 29 May 2026 15:16:43 +0100 Subject: [PATCH 125/173] Update README.md to add note re v1.6.3 regression (#618) Added note regarding v1.6.3. --- aws-lambda-java-log4j2/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws-lambda-java-log4j2/README.md b/aws-lambda-java-log4j2/README.md index 480df21df..d8e40ebea 100644 --- a/aws-lambda-java-log4j2/README.md +++ b/aws-lambda-java-log4j2/README.md @@ -1,5 +1,7 @@ # Using log4j2 with AWS Lambda +**IMPORTANT: The v1.6.3 release contained a regression (see [#612](https://github.com/aws/aws-lambda-java-libs/issues/612)) resulting in missing logs. Please upgrade to v1.6.4 or later. We apologize for the inconvenience.** + ### 1. Pull in log4j2 dependencies Example for Maven pom.xml From 845ae2a86b8ee4db8c8037b8c29b49d510ffd448 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Mon, 29 Jun 2026 12:11:22 +0100 Subject: [PATCH 126/173] Add end-to-end integration tests --- .github/workflows/build-integration-test.yml | 56 ++++++++++ .github/workflows/run-integration-test.yml | 102 +++++++++++++++++ .../log4j2-test-function/pom.xml | 77 +++++++++++++ .../main/java/integ/Log4j2TestHandler.java | 30 +++++ .../src/main/resources/log4j2.xml | 17 +++ lambda-integration-tests/run-tests.sh | 103 ++++++++++++++++++ lambda-integration-tests/samconfig.toml | 24 ++++ lambda-integration-tests/template.yaml | 34 ++++++ 8 files changed, 443 insertions(+) create mode 100644 .github/workflows/build-integration-test.yml create mode 100644 .github/workflows/run-integration-test.yml create mode 100644 lambda-integration-tests/log4j2-test-function/pom.xml create mode 100644 lambda-integration-tests/log4j2-test-function/src/main/java/integ/Log4j2TestHandler.java create mode 100644 lambda-integration-tests/log4j2-test-function/src/main/resources/log4j2.xml create mode 100755 lambda-integration-tests/run-tests.sh create mode 100644 lambda-integration-tests/samconfig.toml create mode 100644 lambda-integration-tests/template.yaml diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml new file mode 100644 index 000000000..5f5e82d13 --- /dev/null +++ b/.github/workflows/build-integration-test.yml @@ -0,0 +1,56 @@ +# this workflow verifies that the integration test Lambda function builds successfully. +# it does NOT deploy or run the tests (that requires AWS credentials and is done in +# run-integration-test.yml). + +name: Build integration tests + +on: + push: + branches: [ main ] + paths: + - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' + - 'lambda-integration-tests/**' + pull_request: + branches: [ '*' ] + paths: + - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' + - 'lambda-integration-tests/**' + - '.github/workflows/build-integration-test.yml' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: | + 8 + 21 + distribution: corretto + cache: maven + + - name: Install core with Maven + run: | + export JAVA_HOME=$JAVA_HOME_8_X64 + mvn -B install --file aws-lambda-java-core/pom.xml + + - name: Install log4j2 with Maven + run: | + export JAVA_HOME=$JAVA_HOME_8_X64 + mvn -B install --file aws-lambda-java-log4j2/pom.xml + + # build the integration test function + # this verifies that the function compiles and packages correctly. + # the tests will run in run-integration-test.yml which deploys to AWS. + - name: Package integration test function + run: | + export JAVA_HOME=$JAVA_HOME_21_X64 + mvn -B package --file lambda-integration-tests/log4j2-test-function/pom.xml diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml new file mode 100644 index 000000000..667e76d96 --- /dev/null +++ b/.github/workflows/run-integration-test.yml @@ -0,0 +1,102 @@ +# this workflow deploys a Lambda function that uses aws-lambda-java-log4j2, +# invokes it, and verifies that logs arrive in CloudWatch. + +name: Run integration tests + +permissions: + id-token: write + contents: read + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'aws-lambda-java-log4j2/**' + - 'aws-lambda-java-core/**' + - 'lambda-integration-tests/**' + +jobs: + run-integration-tests: + # Only run on the main repo, not forks + if: ${{ github.repository_owner == 'aws' }} + runs-on: ubuntu-latest + concurrency: + group: integration-test + cancel-in-progress: false + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: | + 8 + 21 + distribution: corretto + cache: maven + + - name: Install SAM CLI + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v6.0.0 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ secrets.ROLE_SESSION_NAME }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Install core with Maven + run: | + export JAVA_HOME=$JAVA_HOME_8_X64 + mvn -B install --file aws-lambda-java-core/pom.xml + + - name: Install log4j2 with Maven + run: | + export JAVA_HOME=$JAVA_HOME_8_X64 + mvn -B install --file aws-lambda-java-log4j2/pom.xml + + - name: Build SAM stack + run: | + export JAVA_HOME=$JAVA_HOME_21_X64 + cd lambda-integration-tests && sam build + + - name: Validate SAM stack + run: cd lambda-integration-tests && sam validate --lint + + - name: Deploy stack + id: deploy_stack + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd lambda-integration-tests + stackName="aws-lambda-java-log4j2-integ-test-$GITHUB_RUN_ID" + echo "STACK_NAME=$stackName" >> "$GITHUB_OUTPUT" + echo "Stack name = $stackName" + sam deploy \ + --stack-name "${stackName}" \ + --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }}" \ + --no-confirm-changeset \ + --no-progressbar \ + --resolve-s3 \ + --capabilities CAPABILITY_IAM \ + 2>&1 | tee /tmp/sam-deploy.log | tail -n 20 + LOG4J2_TEST_FUNCTION=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | select(.OutputKey=="Log4j2TestFunction") | .OutputValue') + echo "LOG4J2_TEST_FUNCTION=$LOG4J2_TEST_FUNCTION" >> "$GITHUB_OUTPUT" + echo "Function name: $LOG4J2_TEST_FUNCTION" + + - name: Run integration test + env: + LOG4J2_TEST_FUNCTION: ${{ steps.deploy_stack.outputs.LOG4J2_TEST_FUNCTION }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: ./lambda-integration-tests/run-tests.sh + + - name: Cleanup + if: always() && steps.deploy_stack.outputs.STACK_NAME + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + STACK_NAME: ${{ steps.deploy_stack.outputs.STACK_NAME }} + run: | + sam delete --stack-name "${STACK_NAME}" --no-prompts --region "${AWS_REGION}" diff --git a/lambda-integration-tests/log4j2-test-function/pom.xml b/lambda-integration-tests/log4j2-test-function/pom.xml new file mode 100644 index 000000000..b036d1dea --- /dev/null +++ b/lambda-integration-tests/log4j2-test-function/pom.xml @@ -0,0 +1,77 @@ + + 4.0.0 + + com.amazonaws + log4j2-integration-test-function + 1.0.0 + jar + + Log4j2 Integration Test Function + + Lambda function used to verify that aws-lambda-java-log4j2 correctly emits logs to CloudWatch. + + + + 21 + 21 + UTF-8 + 2.25.4 + + + + + com.amazonaws + aws-lambda-java-core + 1.4.0 + + + com.amazonaws + aws-lambda-java-log4j2 + 1.6.4 + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.1 + + + package + + shade + + + + + + + + + + + + com.github.edwgiz + maven-shade-plugin.log4j2-cachefile-transformer + 2.8.1 + + + + + + diff --git a/lambda-integration-tests/log4j2-test-function/src/main/java/integ/Log4j2TestHandler.java b/lambda-integration-tests/log4j2-test-function/src/main/java/integ/Log4j2TestHandler.java new file mode 100644 index 000000000..d81a3fa27 --- /dev/null +++ b/lambda-integration-tests/log4j2-test-function/src/main/java/integ/Log4j2TestHandler.java @@ -0,0 +1,30 @@ +package integ; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + +/** + * integration test handler that logs a marker string using Log4j2 with the LambdaAppender. + * the test verifies that the marker appears in CloudWatch Logs, confirming end-to-end + * log delivery through the aws-lambda-java-log4j2 library. + */ +public class Log4j2TestHandler implements RequestHandler, String> { + + private static final Logger logger = LogManager.getLogger(Log4j2TestHandler.class); + + @Override + public String handleRequest(Map event, Context context) { + String marker = event.getOrDefault("marker", "NO_MARKER_PROVIDED"); + + logger.info("INTEG_TEST_MARKER: {}", marker); + logger.debug("Debug level message with marker: {}", marker); + logger.warn("Warning level message with marker: {}", marker); + logger.error("Error level message with marker: {}", marker); + + return "OK:" + marker; + } +} diff --git a/lambda-integration-tests/log4j2-test-function/src/main/resources/log4j2.xml b/lambda-integration-tests/log4j2-test-function/src/main/resources/log4j2.xml new file mode 100644 index 000000000..1cbc36bd0 --- /dev/null +++ b/lambda-integration-tests/log4j2-test-function/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + + diff --git a/lambda-integration-tests/run-tests.sh b/lambda-integration-tests/run-tests.sh new file mode 100755 index 000000000..844cc5655 --- /dev/null +++ b/lambda-integration-tests/run-tests.sh @@ -0,0 +1,103 @@ +# integration test script for aws-lambda-java-log4j2. +# invokes the deployed lambda function and verifies logs appear in CloudWatch. + +set -euo pipefail + +FUNCTION_NAME="${LOG4J2_TEST_FUNCTION:?LOG4J2_TEST_FUNCTION env var is required}" +REGION="${AWS_REGION:?AWS_REGION env var is required}" +MARKER="integ-test-$(date +%s)-${RANDOM}" + +echo "=== Log4j2 Integration Test ===" +echo "Function: ${FUNCTION_NAME}" +echo "Region: ${REGION}" +echo "Marker: ${MARKER}" +echo "" + +# invoke the lambda function +echo ">>> Invoking Lambda function..." +INVOKE_OUTPUT=$(aws lambda invoke \ + --function-name "${FUNCTION_NAME}" \ + --region "${REGION}" \ + --payload "{\"marker\": \"${MARKER}\"}" \ + --cli-binary-format raw-in-base64-out \ + --output json \ + /tmp/integ-test-response.json) || { + echo "FAIL: aws lambda invoke command failed with exit code $?" + echo "Output: ${INVOKE_OUTPUT:-}" + exit 1 +} + +echo "Invoke output: ${INVOKE_OUTPUT}" +RESPONSE=$(cat /tmp/integ-test-response.json) +echo "Response payload: ${RESPONSE}" + +# check for lambda execution errors +FUNCTION_ERROR=$(echo "${INVOKE_OUTPUT}" | jq -r '.FunctionError // empty') +if [ -n "${FUNCTION_ERROR}" ]; then + echo "FAIL: Lambda function returned an execution error (FunctionError: ${FUNCTION_ERROR})" + echo "Error response: ${RESPONSE}" + exit 1 +fi + +# verify the function executed successfully +if echo "${RESPONSE}" | grep -q "OK:${MARKER}"; then + echo ">>> Function invocation successful." +else + echo "FAIL: Unexpected response from Lambda function." + echo "Expected response containing: OK:${MARKER}" + echo "Got: ${RESPONSE}" + exit 1 +fi + +# query CloudWatch logs for the marker +LOG_GROUP="/aws/lambda/${FUNCTION_NAME}" +echo "" +echo ">>> Querying CloudWatch Logs group: ${LOG_GROUP}" + +MAX_ATTEMPTS=5 +WAIT_SECONDS=10 +FOUND=false + +for attempt in $(seq 1 $MAX_ATTEMPTS); do + echo ">>> Attempt ${attempt}/${MAX_ATTEMPTS}: waiting ${WAIT_SECONDS}s for log propagation..." + sleep "${WAIT_SECONDS}" + + LOGS_OUTPUT=$(aws logs filter-log-events \ + --log-group-name "${LOG_GROUP}" \ + --region "${REGION}" \ + --filter-pattern "\"INTEG_TEST_MARKER\" \"${MARKER}\"" \ + --start-time $(($(date +%s) * 1000 - 120000)) \ + --output json 2>&1) + + if echo "${LOGS_OUTPUT}" | grep -q "INTEG_TEST_MARKER: ${MARKER}"; then + FOUND=true + break + fi + + echo " Marker not found yet." + WAIT_SECONDS=$((WAIT_SECONDS * 2)) +done + +# verify the marker was found +if [ "${FOUND}" = true ]; then + echo "" + echo "=== PASS: Log4j2 integration test succeeded ===" + echo "The marker '${MARKER}' was found in CloudWatch Logs (attempt ${attempt})." + echo "This confirms that the LambdaAppender plugin was discovered by Log4j2" + echo "and logs are being delivered to CloudWatch correctly." +else + echo "" + echo "=== FAIL: Log4j2 integration test failed ===" + echo "The marker '${MARKER}' was NOT found in CloudWatch Logs after ${MAX_ATTEMPTS} attempts." + echo "This indicates that the LambdaAppender was not discovered by Log4j2," + echo "likely due to a missing Log4j2Plugins.dat in the packaged JAR." + echo "" + echo "Dumping all recent log events for debugging:" + aws logs filter-log-events \ + --log-group-name "${LOG_GROUP}" \ + --region "${REGION}" \ + --start-time $(($(date +%s) * 1000 - 120000)) \ + --limit 50 \ + --output text 2>&1 || true + exit 1 +fi diff --git a/lambda-integration-tests/samconfig.toml b/lambda-integration-tests/samconfig.toml new file mode 100644 index 000000000..5e6597861 --- /dev/null +++ b/lambda-integration-tests/samconfig.toml @@ -0,0 +1,24 @@ +version = 0.1 + +[default] +[default.build.parameters] +cached = true +parallel = true +build_in_source = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +resolve_s3 = true + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml new file mode 100644 index 000000000..101e586e0 --- /dev/null +++ b/lambda-integration-tests/template.yaml @@ -0,0 +1,34 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: aws-lambda-java-log4j2 integration tests + +Parameters: + LambdaRole: + Type: String + +Globals: + Function: + Timeout: 30 + MemorySize: 512 + +Resources: + Log4j2TestFunction: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: java21 + Properties: + CodeUri: log4j2-test-function/ + Handler: integ.Log4j2TestHandler::handleRequest + Runtime: java21 + Role: !Ref LambdaRole + Environment: + Variables: + AWS_LAMBDA_LOG_FORMAT: TEXT + +Outputs: + Log4j2TestFunction: + Description: "Log4j2 integration test function name" + Value: !Ref Log4j2TestFunction + Log4j2TestFunctionArn: + Description: "Log4j2 integration test function ARN" + Value: !GetAtt Log4j2TestFunction.Arn From 76ec4ed320237b95fdc6f391d9003dd63f505a63 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 30 Jun 2026 15:48:49 +0100 Subject: [PATCH 127/173] Pin GitHub Actions to commit SHAs --- .github/workflows/build-integration-test.yml | 4 ++-- .github/workflows/run-integration-test.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 5f5e82d13..748e0e1eb 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -26,10 +26,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: java-version: | 8 diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 667e76d96..bd77684e3 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -25,10 +25,10 @@ jobs: group: integration-test cancel-in-progress: false steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: java-version: | 8 @@ -37,12 +37,12 @@ jobs: cache: maven - name: Install SAM CLI - uses: aws-actions/setup-sam@v2 + uses: aws-actions/setup-sam@f84ec7d548307efafe33230528756de3c5841a17 # v2 with: use-installer: true - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} role-session-name: ${{ secrets.ROLE_SESSION_NAME }} From 9610b52294854aa895872a61d5aeb34cdd65af5d Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 1 Jul 2026 10:59:28 +0100 Subject: [PATCH 128/173] fix: run integ test on both architectures --- .github/test-matrix.json | 14 ++++++++++ .github/workflows/build-integration-test.yml | 20 +++++++++++++- .github/workflows/run-integration-test.yml | 28 ++++++++++++++++---- lambda-integration-tests/template.yaml | 8 ++++++ 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 .github/test-matrix.json diff --git a/.github/test-matrix.json b/.github/test-matrix.json new file mode 100644 index 000000000..ef6ea1b9e --- /dev/null +++ b/.github/test-matrix.json @@ -0,0 +1,14 @@ +{ + "arch": [ + { + "runner": "ubuntu-latest", + "label": "x64", + "sam_arch": "x86_64" + }, + { + "runner": "ubuntu-24.04-arm", + "label": "arm64", + "sam_arch": "arm64" + } + ] +} diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 748e0e1eb..e9e5a3adf 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -23,8 +23,26 @@ permissions: contents: read jobs: - build: + load-matrix: runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Load test matrix + id: set + run: | + MATRIX=$(jq -c '.' .github/test-matrix.json) + echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT" + + build: + needs: load-matrix + runs-on: ${{ matrix.arch.runner }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.load-matrix.outputs.matrix) }} + name: "build (${{ matrix.arch.label }})" steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index bd77684e3..a43bd2112 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -17,12 +17,30 @@ on: - 'lambda-integration-tests/**' jobs: + load-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set.outputs.matrix }} + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Load test matrix + id: set + run: | + MATRIX=$(jq -c '.' .github/test-matrix.json) + echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT" + run-integration-tests: - # Only run on the main repo, not forks + needs: load-matrix + # Only run on the main repo, not forks if: ${{ github.repository_owner == 'aws' }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.arch.runner }} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.load-matrix.outputs.matrix) }} + name: "integration-test (${{ matrix.arch.label }})" concurrency: - group: integration-test + group: integration-test-${{ matrix.arch.label }} cancel-in-progress: false steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -72,12 +90,12 @@ jobs: AWS_REGION: ${{ secrets.AWS_REGION }} run: | cd lambda-integration-tests - stackName="aws-lambda-java-log4j2-integ-test-$GITHUB_RUN_ID" + stackName="aws-lambda-java-log4j2-integ-test-${{ matrix.arch.label }}-$GITHUB_RUN_ID" echo "STACK_NAME=$stackName" >> "$GITHUB_OUTPUT" echo "Stack name = $stackName" sam deploy \ --stack-name "${stackName}" \ - --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }}" \ + --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }} ParameterKey=Architecture,ParameterValue=${{ matrix.arch.sam_arch }}" \ --no-confirm-changeset \ --no-progressbar \ --resolve-s3 \ diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index 101e586e0..01a10a706 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -5,6 +5,12 @@ Description: aws-lambda-java-log4j2 integration tests Parameters: LambdaRole: Type: String + Architecture: + Type: String + Default: x86_64 + AllowedValues: + - x86_64 + - arm64 Globals: Function: @@ -20,6 +26,8 @@ Resources: CodeUri: log4j2-test-function/ Handler: integ.Log4j2TestHandler::handleRequest Runtime: java21 + Architectures: + - !Ref Architecture Role: !Ref LambdaRole Environment: Variables: From 0e35558c79d389ab4d31a6bb33270f8264e2faf8 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 1 Jul 2026 11:34:09 +0100 Subject: [PATCH 129/173] fix: changing to precreated bucket --- .github/workflows/build-integration-test.yml | 14 +++++++++++++- .github/workflows/run-integration-test.yml | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index e9e5a3adf..8a77db7d4 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -36,7 +36,7 @@ jobs: MATRIX=$(jq -c '.' .github/test-matrix.json) echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT" - build: + build-arch: needs: load-matrix runs-on: ${{ matrix.arch.runner }} strategy: @@ -72,3 +72,15 @@ jobs: run: | export JAVA_HOME=$JAVA_HOME_21_X64 mvn -B package --file lambda-integration-tests/log4j2-test-function/pom.xml + + build: + needs: build-arch + if: always() + runs-on: ubuntu-latest + steps: + - name: Check build results + run: | + if [ "${{ needs.build-arch.result }}" != "success" ]; then + echo "Build failed on one or more architectures" + exit 1 + fi diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index a43bd2112..6115dd528 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -98,7 +98,7 @@ jobs: --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }} ParameterKey=Architecture,ParameterValue=${{ matrix.arch.sam_arch }}" \ --no-confirm-changeset \ --no-progressbar \ - --resolve-s3 \ + --s3-bucket "${{ secrets.S3_BUCKET }}" \ --capabilities CAPABILITY_IAM \ 2>&1 | tee /tmp/sam-deploy.log | tail -n 20 LOG4J2_TEST_FUNCTION=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | select(.OutputKey=="Log4j2TestFunction") | .OutputValue') From cb9c4f6d70a19a7e9f7e1d3e5d2e69cd3c7062c1 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 1 Jul 2026 15:19:18 +0100 Subject: [PATCH 130/173] fix: renaming secrets --- .github/workflows/run-integration-test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 6115dd528..75a2f11c0 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -62,9 +62,9 @@ jobs: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 with: - role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} - role-session-name: ${{ secrets.ROLE_SESSION_NAME }} - aws-region: ${{ secrets.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_ROLE_LOG4J2_INTEG_TEST }} + role-session-name: GitHubActionsLog4j2IntegTest + aws-region: ${{ secrets.AWS_REGION_LOG4J2_INTEG_TEST }} - name: Install core with Maven run: | @@ -87,7 +87,7 @@ jobs: - name: Deploy stack id: deploy_stack env: - AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_REGION: ${{ secrets.AWS_REGION_LOG4J2_INTEG_TEST }} run: | cd lambda-integration-tests stackName="aws-lambda-java-log4j2-integ-test-${{ matrix.arch.label }}-$GITHUB_RUN_ID" @@ -95,10 +95,10 @@ jobs: echo "Stack name = $stackName" sam deploy \ --stack-name "${stackName}" \ - --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }} ParameterKey=Architecture,ParameterValue=${{ matrix.arch.sam_arch }}" \ + --parameter-overrides "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE_LOG4J2_INTEG_TEST }} ParameterKey=Architecture,ParameterValue=${{ matrix.arch.sam_arch }}" \ --no-confirm-changeset \ --no-progressbar \ - --s3-bucket "${{ secrets.S3_BUCKET }}" \ + --s3-bucket "${{ secrets.S3_BUCKET_LOG4J2_INTEG_TEST }}" \ --capabilities CAPABILITY_IAM \ 2>&1 | tee /tmp/sam-deploy.log | tail -n 20 LOG4J2_TEST_FUNCTION=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | select(.OutputKey=="Log4j2TestFunction") | .OutputValue') @@ -108,13 +108,13 @@ jobs: - name: Run integration test env: LOG4J2_TEST_FUNCTION: ${{ steps.deploy_stack.outputs.LOG4J2_TEST_FUNCTION }} - AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_REGION: ${{ secrets.AWS_REGION_LOG4J2_INTEG_TEST }} run: ./lambda-integration-tests/run-tests.sh - name: Cleanup if: always() && steps.deploy_stack.outputs.STACK_NAME env: - AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_REGION: ${{ secrets.AWS_REGION_LOG4J2_INTEG_TEST }} STACK_NAME: ${{ steps.deploy_stack.outputs.STACK_NAME }} run: | sam delete --stack-name "${STACK_NAME}" --no-prompts --region "${AWS_REGION}" From 361bbfcc17ddc1b122956f52dccb956e56004c7e Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Thu, 2 Jul 2026 14:48:24 +0100 Subject: [PATCH 131/173] fix: use architecture-aware JAVA_HOME on matrix runners --- .github/test-matrix.json | 6 ++++-- .github/workflows/build-integration-test.yml | 6 +++--- .github/workflows/run-integration-test.yml | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/test-matrix.json b/.github/test-matrix.json index ef6ea1b9e..7e6539cb6 100644 --- a/.github/test-matrix.json +++ b/.github/test-matrix.json @@ -3,12 +3,14 @@ { "runner": "ubuntu-latest", "label": "x64", - "sam_arch": "x86_64" + "sam_arch": "x86_64", + "java_suffix": "X64" }, { "runner": "ubuntu-24.04-arm", "label": "arm64", - "sam_arch": "arm64" + "sam_arch": "arm64", + "java_suffix": "ARM64" } ] } diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 8a77db7d4..2a6bb30c5 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -57,12 +57,12 @@ jobs: - name: Install core with Maven run: | - export JAVA_HOME=$JAVA_HOME_8_X64 + export JAVA_HOME=$JAVA_HOME_8_${{ matrix.arch.java_suffix }} mvn -B install --file aws-lambda-java-core/pom.xml - name: Install log4j2 with Maven run: | - export JAVA_HOME=$JAVA_HOME_8_X64 + export JAVA_HOME=$JAVA_HOME_8_${{ matrix.arch.java_suffix }} mvn -B install --file aws-lambda-java-log4j2/pom.xml # build the integration test function @@ -70,7 +70,7 @@ jobs: # the tests will run in run-integration-test.yml which deploys to AWS. - name: Package integration test function run: | - export JAVA_HOME=$JAVA_HOME_21_X64 + export JAVA_HOME=$JAVA_HOME_21_${{ matrix.arch.java_suffix }} mvn -B package --file lambda-integration-tests/log4j2-test-function/pom.xml build: diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 75a2f11c0..1b857f45c 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -68,17 +68,17 @@ jobs: - name: Install core with Maven run: | - export JAVA_HOME=$JAVA_HOME_8_X64 + export JAVA_HOME=$JAVA_HOME_8_${{ matrix.arch.java_suffix }} mvn -B install --file aws-lambda-java-core/pom.xml - name: Install log4j2 with Maven run: | - export JAVA_HOME=$JAVA_HOME_8_X64 + export JAVA_HOME=$JAVA_HOME_8_${{ matrix.arch.java_suffix }} mvn -B install --file aws-lambda-java-log4j2/pom.xml - name: Build SAM stack run: | - export JAVA_HOME=$JAVA_HOME_21_X64 + export JAVA_HOME=$JAVA_HOME_21_${{ matrix.arch.java_suffix }} cd lambda-integration-tests && sam build - name: Validate SAM stack From cffc264baa32c1735c86e91e89e65810024307ff Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Thu, 2 Jul 2026 17:02:17 +0100 Subject: [PATCH 132/173] fix: remove resolve_s3 from samconfig to avoid conflict with --s3-bucket --- lambda-integration-tests/samconfig.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/lambda-integration-tests/samconfig.toml b/lambda-integration-tests/samconfig.toml index 5e6597861..f1f665e48 100644 --- a/lambda-integration-tests/samconfig.toml +++ b/lambda-integration-tests/samconfig.toml @@ -12,7 +12,6 @@ lint = true [default.deploy.parameters] capabilities = "CAPABILITY_IAM" confirm_changeset = true -resolve_s3 = true [default.sync.parameters] watch = true From fff1ffc83f3fcc0314f76c798d9963b337b2bff6 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Thu, 2 Jul 2026 18:59:13 +0100 Subject: [PATCH 133/173] chore: introducing functionName --- lambda-integration-tests/template.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index 01a10a706..a12e5f656 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -23,6 +23,7 @@ Resources: Metadata: BuildMethod: java21 Properties: + FunctionName: !Sub "${AWS::StackName}-fn" CodeUri: log4j2-test-function/ Handler: integ.Log4j2TestHandler::handleRequest Runtime: java21 From 3af9bdf819844738ccf632f695dd06f98d3c5eb1 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 3 Jul 2026 10:56:42 +0100 Subject: [PATCH 134/173] fix: verify stack status after deploy before running tests --- .github/workflows/run-integration-test.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 1b857f45c..ad46d7fd6 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -101,6 +101,24 @@ jobs: --s3-bucket "${{ secrets.S3_BUCKET_LOG4J2_INTEG_TEST }}" \ --capabilities CAPABILITY_IAM \ 2>&1 | tee /tmp/sam-deploy.log | tail -n 20 + + # Verify stack is in a healthy state + STACK_STATUS=$(aws cloudformation describe-stacks \ + --stack-name "${stackName}" \ + --region "${AWS_REGION}" \ + --query 'Stacks[0].StackStatus' \ + --output text 2>&1) + echo "Stack status: $STACK_STATUS" + if [ "$STACK_STATUS" != "CREATE_COMPLETE" ] && [ "$STACK_STATUS" != "UPDATE_COMPLETE" ]; then + echo "FAIL: Stack is not in a healthy state (status: $STACK_STATUS)" + aws cloudformation describe-stack-events \ + --stack-name "${stackName}" \ + --region "${AWS_REGION}" \ + --query 'StackEvents[?ResourceStatus==`CREATE_FAILED` || ResourceStatus==`UPDATE_FAILED`].[LogicalResourceId,ResourceStatusReason]' \ + --output table 2>&1 || true + exit 1 + fi + LOG4J2_TEST_FUNCTION=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | select(.OutputKey=="Log4j2TestFunction") | .OutputValue') echo "LOG4J2_TEST_FUNCTION=$LOG4J2_TEST_FUNCTION" >> "$GITHUB_OUTPUT" echo "Function name: $LOG4J2_TEST_FUNCTION" From 52bd30d7415eba7b6012c19e4c17cf4f2dfb53dc Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 14 Jul 2026 17:09:13 +0100 Subject: [PATCH 135/173] build: Add maven-release-plugin and SCM config to all modules Add maven-release-plugin configuration with module-specific tag formats and SCM connection details to enable automated Maven deployments across all library modules. --- aws-lambda-java-core/pom.xml | 20 +++++++++++++++++++ .../pom.xml | 15 ++++++++++++++ aws-lambda-java-events/pom.xml | 20 +++++++++++++++++++ aws-lambda-java-log4j2/pom.xml | 15 ++++++++++++++ .../pom.xml | 15 ++++++++++++++ aws-lambda-java-serialization/pom.xml | 15 ++++++++++++++ aws-lambda-java-tests/pom.xml | 15 ++++++++++++++ 7 files changed, 115 insertions(+) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index cca9d0cdf..b0277d3e0 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -36,6 +39,22 @@ 1.8 + + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-core-@{project.version} + true + release + deploy + + + + + dev @@ -114,6 +133,7 @@ true central + true diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 6de599ef7..d05cd63d5 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -24,6 +24,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -79,6 +82,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-sdk-transformer-@{project.version} + true + release + deploy + + maven-surefire-plugin ${maven-surefire-plugin.version} @@ -171,6 +185,7 @@ true central + true diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index c8c40e0c7..9c19cdc05 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -83,6 +86,22 @@
    + + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-@{project.version} + true + release + deploy + + + + + dev @@ -161,6 +180,7 @@ true central + true diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index a03d3d3b6..fd0ce8114 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -22,6 +22,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -77,6 +80,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-log4j2-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-toolchains-plugin @@ -183,6 +197,7 @@ true central + true diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 2ba71c43c..fa580e7d1 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -21,6 +21,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -115,6 +118,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-runtime-interface-client-@{project.version} + true + release + deploy + + maven-install-plugin org.apache.maven.plugins @@ -381,6 +395,7 @@ true central + true diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 60a3c67a9..74b9f669c 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -19,6 +19,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -175,6 +178,7 @@ true central + true @@ -191,6 +195,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-serialization-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-shade-plugin diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 5e5f19b33..c0eef7929 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -20,6 +20,9 @@ https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + scm:git:https://github.com/aws/aws-lambda-java-libs.git + HEAD @@ -236,6 +239,7 @@ true central + true @@ -245,6 +249,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-tests-@{project.version} + true + release + deploy + + org.apache.maven.plugins maven-compiler-plugin From 4acf4aa4f34bb78e04492bb890e419be1209252d Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 14 Jul 2026 17:13:25 +0100 Subject: [PATCH 136/173] fix: Add toolchains.xml to codebuild agent for JDK 8 resolution The maven-toolchains-plugin requires JDK 8 to be declared in ~/.m2/toolchains.xml. The smoke test Docker agent has Corretto 8 installed but was missing this file, causing the build to fail. --- .../test/integration/codebuild-local/Dockerfile.agent | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent index 3dbdb3c68..3ebbbb3a2 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent @@ -11,4 +11,8 @@ COPY --from=docker/buildx-bin:latest /buildx /usr/libexec/docker/cli-plugins/doc ENV PATH="$PATH:/apache-maven/bin" RUN mkdir /apache-maven && \ curl https://archive.apache.org/dist/maven/maven-3/3.8.7/binaries/apache-maven-3.8.7-bin.tar.gz | \ - tar -xz -C /apache-maven --strip-components 1 \ No newline at end of file + tar -xz -C /apache-maven --strip-components 1 + +# Declare JDK 8 in toolchains.xml so maven-toolchains-plugin can resolve it +RUN mkdir -p /root/.m2 && \ + printf '\n\n \n jdk\n \n 8\n \n \n /usr/lib/jvm/java-1.8.0-amazon-corretto\n \n \n\n' > /root/.m2/toolchains.xml From 8a3267a39c8bdf72789eaf1838032532fd90d3c4 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 15 Jul 2026 11:57:02 +0100 Subject: [PATCH 137/173] fix: Use heredoc and $JAVA_HOME for toolchains.xml --- .../integration/codebuild-local/Dockerfile.agent | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent index 3ebbbb3a2..350305e81 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent @@ -15,4 +15,17 @@ RUN mkdir /apache-maven && \ # Declare JDK 8 in toolchains.xml so maven-toolchains-plugin can resolve it RUN mkdir -p /root/.m2 && \ - printf '\n\n \n jdk\n \n 8\n \n \n /usr/lib/jvm/java-1.8.0-amazon-corretto\n \n \n\n' > /root/.m2/toolchains.xml + cat > /root/.m2/toolchains.xml < + + + jdk + + 8 + + + ${JAVA_HOME} + + + +EOF From 3bdc9511c1cb6b56ad0d23d85a9f71c8d1d762b5 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 15 Jul 2026 13:22:22 +0100 Subject: [PATCH 138/173] docs: Add toolchains.xml example and setup guide --- CONTRIBUTING.md | 19 +++++++++++++++++++ toolchains.xml.example | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 toolchains.xml.example diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1241783c..7e8164689 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,25 @@ To send us a pull request, please: 5. Send us a pull request, answering any default questions in the pull request interface. 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +## Build Prerequisites + +This project uses the Maven Toolchains Plugin to pin compilation to JDK 8. If you don't have a `~/.m2/toolchains.xml` configured, builds will fail with: + +``` +No toolchain found for type jdk [ version='[1.8,9)' ] +``` + +To fix this, copy the example file to your Maven config directory and update the path: + +```bash +cp toolchains.xml.example ~/.m2/toolchains.xml +``` + +Then edit `~/.m2/toolchains.xml` and set `` to your local JDK 8 installation path. + +Note: if you use `actions/setup-java` in CI (as our GitHub Actions workflows do), this file is generated automatically. + GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). diff --git a/toolchains.xml.example b/toolchains.xml.example new file mode 100644 index 000000000..b57490761 --- /dev/null +++ b/toolchains.xml.example @@ -0,0 +1,21 @@ + + + + + jdk + + 8 + + + /path/to/your/jdk8 + + + From 544ee077db405850e8c28bc7d926118268a8f58f Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 15 Jul 2026 17:59:03 +0100 Subject: [PATCH 139/173] ci: Add automated Maven Central release workflow Add .github/workflows/release.yml, a workflow_dispatch pipeline that builds, tests, and publishes a single module to Maven Central in one pinned JDK 8 environment, replacing the manual mvn deploy from a developer workstation. The release step prepares locally, publishes, then pushes the commits and module-scoped tag atomically, so a failed publish never leaves an orphan tag on the remote; a failure path rolls back the runner state. Releases are serialized repo-wide via a shared concurrency group. Signing/publishing secrets are not wired yet, so only the dry-run path (skip_publish) works without credentials for now. Set all module versions to -SNAPSHOT as the release plugin's expected starting point, and merge a duplicate top-level section in the events POM introduced when the toolchains change (PR #617) met the release-plugin config on this branch. --- .github/workflows/release.yml | 187 ++++++++++++++++++ aws-lambda-java-core/pom.xml | 2 +- .../pom.xml | 2 +- aws-lambda-java-events/pom.xml | 29 ++- aws-lambda-java-log4j2/pom.xml | 2 +- .../pom.xml | 2 +- aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 8 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..6d899990b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,187 @@ +name: Release to Maven Central + +# Automated release pipeline: builds, tests, and publishes a module to Maven +# Central in one controlled environment. Signing/publishing secrets are still +# to be handled; until then use skip_publish (dry-run), which needs none. + +on: + workflow_dispatch: + inputs: + module: + description: 'Module to release (directory name, e.g. aws-lambda-java-log4j2)' + required: true + type: choice + options: + - aws-lambda-java-core + - aws-lambda-java-events + - aws-lambda-java-events-sdk-transformer + - aws-lambda-java-log4j2 + - aws-lambda-java-runtime-interface-client + - aws-lambda-java-serialization + - aws-lambda-java-tests + releaseVersion: + description: 'Release version override (optional; defaults to the POM version without -SNAPSHOT)' + required: false + type: string + developmentVersion: + description: 'Next development version override (optional, must end with -SNAPSHOT)' + required: false + type: string + skip_publish: + description: 'Skip publish (dry-run validation)' + required: false + type: boolean + default: false + +permissions: + contents: write # push release commits and tag + id-token: write # reserved for future secrets handling + +# Serialize all releases repo-wide to avoid concurrent pushes racing on the +# default branch. Never cancel in-flight: it could leave a half-published state. +concurrency: + group: release + cancel-in-progress: false + +env: + MODULE: ${{ github.event.inputs.module }} + RELEASE_VERSION_INPUT: ${{ github.event.inputs.releaseVersion }} + DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} + # Batch mode + no transfer-progress spam for every Maven call (Maven 3.9+). + MAVEN_ARGS: "-B --no-transfer-progress" + +jobs: + release: + runs-on: ubuntu-latest + environment: Release + timeout-minutes: 30 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 # full history for tagging/pushing + + # Pinned JDK 8: building on a newer JDK can silently break the artifact. + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Validate inputs and resolve versions + run: | + if [[ ! -d "$MODULE" ]]; then + echo "::error::Module directory '$MODULE' does not exist" + exit 1 + fi + if [[ ! -f "$MODULE/pom.xml" ]]; then + echo "::error::No pom.xml found in '$MODULE'" + exit 1 + fi + + # The POM version is the source of truth and must be a SNAPSHOT. + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + # releaseVersion input is an optional override; default strips -SNAPSHOT. + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + + if [[ -n "$DEVELOPMENT_VERSION_INPUT" && "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then + echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" + exit 1 + fi + + # Build the release plugin version args once; reused by both paths. + RELEASE_ARGS="-DreleaseVersion=$EFFECTIVE_RELEASE_VERSION" + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS="$RELEASE_ARGS -DdevelopmentVersion=$DEVELOPMENT_VERSION_INPUT" + fi + + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" + echo "RELEASE_ARGS=$RELEASE_ARGS" >> "$GITHUB_ENV" + echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (POM currently $CURRENT_VERSION)" + + - name: Configure git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # TODO(secrets): signing/publishing credentials are still to be handled. + # Until then, only skip_publish=true (dry-run) works, as it needs none. + + - name: Install intra-repo dependencies + run: | + # Modules that must be installed locally before the target builds. + declare -A DEPS + DEPS[aws-lambda-java-core]="" + DEPS[aws-lambda-java-events]="" + DEPS[aws-lambda-java-serialization]="" + DEPS[aws-lambda-java-log4j2]="aws-lambda-java-core" + DEPS[aws-lambda-java-events-sdk-transformer]="aws-lambda-java-events" + DEPS[aws-lambda-java-runtime-interface-client]="aws-lambda-java-core aws-lambda-java-serialization" + DEPS[aws-lambda-java-tests]="aws-lambda-java-core aws-lambda-java-serialization aws-lambda-java-events" + + DEP_LIST="${DEPS[$MODULE]}" + if [[ -n "$DEP_LIST" ]]; then + for dep in $DEP_LIST; do + echo "::group::Installing dependency: $dep" + mvn install -DskipTests --file "$dep/pom.xml" + echo "::endgroup::" + done + else + echo "::notice::No intra-repo dependencies for $MODULE" + fi + + # No skip-tests option: never release an unverified artifact. + - name: Run tests + run: mvn verify --file "$MODULE/pom.xml" + + # prepare/perform aren't atomic. Prepare locally (no push), publish, then + # push only after the artifact is live, so a failed publish never leaves an + # orphan tag on the remote. On failure, the rollback step cleans the runner. + - name: Release (prepare locally, publish, then push) + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # 1. Prepare locally (no push): create the release commits + tag. + mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" + + # 2. Publish to Maven Central from the local tag. + mvn release:perform -DlocalCheckout=true --file "$MODULE/pom.xml" + + # 3. Push commits and tag atomically (both or neither). + git push --atomic origin \ + "HEAD:${GITHUB_REF_NAME}" \ + "refs/tags/${MODULE}-${EFFECTIVE_RELEASE_VERSION}" + + - name: Dry-run release (prepare only, no publish) + if: ${{ github.event.inputs.skip_publish == 'true' }} + run: | + mvn release:prepare -DdryRun=true $RELEASE_ARGS --file "$MODULE/pom.xml" + mvn release:clean --file "$MODULE/pom.xml" || true + + # Nothing was pushed, so this only cleans the runner for a retry. + - name: Roll back release on failure + if: ${{ failure() && github.event.inputs.skip_publish != 'true' }} + run: | + mvn release:rollback --file "$MODULE/pom.xml" || true + mvn release:clean --file "$MODULE/pom.xml" || true + git tag -d "${MODULE}-${EFFECTIVE_RELEASE_VERSION}" 2>/dev/null || true + echo "::warning::Release failed before publish completed. The remote was not modified; the runner state has been rolled back. Safe to retry." + + - name: Summary + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + TAG_NAME="${MODULE}-${EFFECTIVE_RELEASE_VERSION}" + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Module | \`$MODULE\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index 321098784..e9464e3d1 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-core - 1.4.0 + 1.4.0-SNAPSHOT jar AWS Lambda Java Core Library diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 9b83b2e13..1072f4cc6 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events-sdk-transformer - 3.1.1 + 3.1.1-SNAPSHOT jar AWS Lambda Java Events SDK Transformer Library diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index e5dc3329c..7ab9aa938 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-events - 3.16.1 + 3.16.1-SNAPSHOT jar AWS Lambda Java Events Library @@ -68,6 +68,17 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + aws-lambda-java-events-@{project.version} + true + release + deploy + + @@ -112,22 +123,6 @@ - - - - org.apache.maven.plugins - maven-release-plugin - 3.1.1 - - aws-lambda-java-events-@{project.version} - true - release - deploy - - - - - dev diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index fd0ce8114..432c4f5c4 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-log4j2 - 1.6.4 + 1.6.4-SNAPSHOT jar AWS Lambda Java Log4j 2.x Libraries diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 66e2f2fe2..0cf33d828 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.11.0 + 2.11.0-SNAPSHOT jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 713fdfcb8..613b204c6 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-lambda-java-serialization - 1.4.1 + 1.4.1-SNAPSHOT jar AWS Lambda Java Runtime Serialization diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 3272b3f44..b1daf4105 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -5,7 +5,7 @@ com.amazonaws aws-lambda-java-tests - 1.1.3 + 1.1.3-SNAPSHOT jar AWS Lambda Java Tests From c9e1061841d88ab37b60a5879c97841b7f674b86 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 21 Jul 2026 16:53:06 +0100 Subject: [PATCH 140/173] ci: Wire OIDC secrets and add native RIC release workflow --- .../release-runtime-interface-client.yml | 288 ++++++++++++++++++ .github/workflows/release.yml | 86 ++++-- 2 files changed, 355 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/release-runtime-interface-client.yml diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml new file mode 100644 index 000000000..43090d1b7 --- /dev/null +++ b/.github/workflows/release-runtime-interface-client.yml @@ -0,0 +1,288 @@ +name: Release RIC to Maven Central + +# RIC ships a native JNI lib for 4 targets + a main JAR (5 artifacts). Each +# native lib is built on its own architecture (x86_64 on ubuntu-latest, +# aarch_64 on ubuntu-24.04-arm) instead of emulating with QEMU. A build matrix +# produces the classifier JARs, then one job assembles and publishes them. + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: 'Release version override (optional; defaults to the POM version without -SNAPSHOT)' + required: false + type: string + developmentVersion: + description: 'Next development version override (optional, must end with -SNAPSHOT)' + required: false + type: string + skip_publish: + description: 'Skip publish (dry-run validation)' + required: false + type: boolean + default: false + +permissions: + contents: write # push release commit and tag + id-token: write # assume the OIDC role for secret retrieval + +# Share the repo-wide "release" group with release.yml so RIC and the pure-Java +# modules can never publish concurrently. Never cancel in-flight: it could leave +# a half-published state. +concurrency: + group: release + cancel-in-progress: false + +env: + MODULE: aws-lambda-java-runtime-interface-client + RELEASE_VERSION_INPUT: ${{ github.event.inputs.releaseVersion }} + DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} + MAVEN_ARGS: "-B --no-transfer-progress" + AWS_REGION: ${{ vars.AWS_REGION_MAVEN_RELEASE }} + OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} + +jobs: + # Build each architecture's native libs (glibc + musl) on a native runner. + build-natives: + strategy: + fail-fast: true + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + profiles: linux-x86_64 linux_musl-x86_64 + - arch: aarch64 + runner: ubuntu-24.04-arm + profiles: linux-aarch64 linux_musl-aarch64 + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Resolve release version + run: | + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + echo "EFFECTIVE_RELEASE_VERSION=${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" >> "$GITHUB_ENV" + + # -DskipTests: only installed so the module compiles, not released here. + - name: Install intra-repo dependencies + run: | + for dep in aws-lambda-java-core aws-lambda-java-serialization; do + mvn install -DskipTests --file "$dep/pom.xml" + done + + # Build at the release version (matches the JAR names the release job + # attaches). + - name: Build native classifier JARs (${{ matrix.arch }}) + env: + IS_JAVA_8: true + run: | + mvn versions:set -DnewVersion="$EFFECTIVE_RELEASE_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + for profile in ${{ matrix.profiles }}; do + echo "::group::Building $profile" + mvn package -P "$profile" -DmultiArch=false -DskipTests --file "$MODULE/pom.xml" + echo "::endgroup::" + done + + # JARs to attach + .so files to assemble the fat main JAR. + - name: Upload native artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ric-natives-${{ matrix.arch }} + if-no-files-found: error + path: | + ${{ env.MODULE }}/target/*-linux*.jar + ${{ env.MODULE }}/target/classes/jni/*.so + + # Assemble all native builds and publish. + release: + needs: build-natives + runs-on: ubuntu-latest + environment: Release + timeout-minutes: 30 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 # full history for tagging/pushing + + - name: Set up JDK 8 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: 8 + distribution: corretto + cache: maven + + - name: Validate inputs and resolve versions + run: | + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + + # Next development version: use the override, or bump the patch. + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + if [[ "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then + echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" + exit 1 + fi + NEXT_DEV_VERSION="$DEVELOPMENT_VERSION_INPUT" + else + IFS='.' read -r MA MI PA <<< "$EFFECTIVE_RELEASE_VERSION" + NEXT_DEV_VERSION="${MA}.${MI}.$((PA + 1))-SNAPSHOT" + fi + + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" + echo "NEXT_DEV_VERSION=$NEXT_DEV_VERSION" >> "$GITHUB_ENV" + echo "TAG_NAME=${MODULE}-${EFFECTIVE_RELEASE_VERSION}" >> "$GITHUB_ENV" + echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (next dev $NEXT_DEV_VERSION)" + + - name: Configure git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # -DskipTests: only installed so the module compiles, not released here. + - name: Install intra-repo dependencies + run: | + for dep in aws-lambda-java-core aws-lambda-java-serialization; do + mvn install -DskipTests --file "$dep/pom.xml" + done + + - name: Set release version + run: mvn versions:set -DnewVersion="$EFFECTIVE_RELEASE_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + + # Test gate before publish. + - name: Run tests + env: + IS_JAVA_8: true + run: mvn test --file "$MODULE/pom.xml" + + # JARs to attach + .so files for the fat main JAR. + - name: Download native artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + pattern: ric-natives-* + path: ric-natives + + - name: Stage native artifacts + run: | + mkdir -p "$MODULE/target/classes/jni" + find ric-natives -name '*.jar' -exec cp {} "$MODULE/target/" \; + find ric-natives -name '*.so' -exec cp {} "$MODULE/target/classes/jni/" \; + echo "Staged native artifacts:" + ls -1 "$MODULE/target/"*-linux*.jar "$MODULE/target/classes/jni/"*.so + + - name: Configure AWS credentials (OIDC) + if: ${{ github.event.inputs.skip_publish != 'true' }} + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ env.OIDC_ROLE_ARN }} + role-session-name: GitHubActionsRicMavenCentralRelease + role-duration-seconds: 3600 + + - name: Fetch signing key and Sonatype credentials + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + + GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") + GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") + SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") + SONATYPE_PASSWORD=$(jq -r '."maven-central-password"' <<< "$CREDS_JSON") + echo "::add-mask::$GPG_PASSPHRASE" + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + + # Import the key with loopback pinentry so Maven can sign non-interactively. + GNUPGHOME=$(mktemp -d) + chmod 700 "$GNUPGHOME" + echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" + echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" + export GNUPGHOME + gpgconf --kill gpg-agent || true + gpg --batch --import <<< "$GPG_PRIVATE_KEY" + GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') + + # settings.xml with the Sonatype token (server id "central"). + SETTINGS="$RUNNER_TEMP/settings.xml" + { + echo '' + echo "central" + echo "${SONATYPE_USERNAME}" + echo "${SONATYPE_PASSWORD}" + echo '' + } > "$SETTINGS" + + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" + echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" + echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + + # -DmultiArch=false builds only the host .so; the aarch_64 .so is already + # staged, so the main JAR still bundles all four. build-helper attaches + # the staged classifier JARs. Gate already ran, so -DskipTests. + - name: Publish to Maven Central + if: ${{ github.event.inputs.skip_publish != 'true' }} + env: + IS_JAVA_8: true + run: | + mvn deploy -Prelease -DskipTests -DmultiArch=false \ + -s "$MAVEN_SETTINGS" \ + -Dgpg.keyname="$GPG_KEYNAME" -Dgpg.passphrase="$GPG_PASSPHRASE" \ + --file "$MODULE/pom.xml" + + - name: Tag and push (only after publish succeeds) + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + git commit -am "chore(ric): release ${EFFECTIVE_RELEASE_VERSION}" + git tag "$TAG_NAME" + mvn versions:set -DnewVersion="$NEXT_DEV_VERSION" -DgenerateBackupPoms=false --file "$MODULE/pom.xml" + git commit -am "chore(ric): prepare next development ${NEXT_DEV_VERSION}" + git push --atomic origin "HEAD:${GITHUB_REF_NAME}" "refs/tags/${TAG_NAME}" + + # Dry-run: validate assembly, no publish/push. + - name: Dry-run assemble (no publish) + if: ${{ github.event.inputs.skip_publish == 'true' }} + env: + IS_JAVA_8: true + run: mvn package -DskipTests -DmultiArch=false --file "$MODULE/pom.xml" + + # Nothing was pushed, so this only cleans the runner. + - name: Roll back local tag on failure + if: ${{ failure() && github.event.inputs.skip_publish != 'true' }} + run: | + git tag -d "$TAG_NAME" 2>/dev/null || true + echo "::warning::Release failed. The remote was not modified; safe to retry." + + - name: Summary + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Module | \`$MODULE\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY + echo "| Artifacts | main JAR + linux/linux_musl x x86_64/aarch_64 classifier JARs |" >> $GITHUB_STEP_SUMMARY + echo "| Built natively | x86_64 on ubuntu-latest, aarch_64 on ubuntu-24.04-arm (no QEMU) |" >> $GITHUB_STEP_SUMMARY + echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d899990b..e148c0131 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,6 @@ name: Release to Maven Central -# Automated release pipeline: builds, tests, and publishes a module to Maven -# Central in one controlled environment. Signing/publishing secrets are still -# to be handled; until then use skip_publish (dry-run), which needs none. +# Builds, tests, and publishes a module to Maven Central in one environment. on: workflow_dispatch: @@ -11,12 +9,14 @@ on: description: 'Module to release (directory name, e.g. aws-lambda-java-log4j2)' required: true type: choice + # aws-lambda-java-runtime-interface-client is intentionally excluded: it + # ships a cross-compiled JNI native library and has its own dedicated + # pipeline, .github/workflows/release-runtime-interface-client.yml. options: - aws-lambda-java-core - aws-lambda-java-events - aws-lambda-java-events-sdk-transformer - aws-lambda-java-log4j2 - - aws-lambda-java-runtime-interface-client - aws-lambda-java-serialization - aws-lambda-java-tests releaseVersion: @@ -34,8 +34,8 @@ on: default: false permissions: - contents: write # push release commits and tag - id-token: write # reserved for future secrets handling + contents: write + id-token: write # Serialize all releases repo-wide to avoid concurrent pushes racing on the # default branch. Never cancel in-flight: it could leave a half-published state. @@ -49,6 +49,8 @@ env: DEVELOPMENT_VERSION_INPUT: ${{ github.event.inputs.developmentVersion }} # Batch mode + no transfer-progress spam for every Maven call (Maven 3.9+). MAVEN_ARGS: "-B --no-transfer-progress" + AWS_REGION: ${{ vars.AWS_REGION_MAVEN_RELEASE }} + OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} jobs: release: @@ -111,19 +113,16 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # TODO(secrets): signing/publishing credentials are still to be handled. - # Until then, only skip_publish=true (dry-run) works, as it needs none. - - name: Install intra-repo dependencies run: | - # Modules that must be installed locally before the target builds. + # Installed so the target compiles. -DskipTests: not released here, + # only the target module gets the full verify gate below. declare -A DEPS DEPS[aws-lambda-java-core]="" DEPS[aws-lambda-java-events]="" DEPS[aws-lambda-java-serialization]="" DEPS[aws-lambda-java-log4j2]="aws-lambda-java-core" DEPS[aws-lambda-java-events-sdk-transformer]="aws-lambda-java-events" - DEPS[aws-lambda-java-runtime-interface-client]="aws-lambda-java-core aws-lambda-java-serialization" DEPS[aws-lambda-java-tests]="aws-lambda-java-core aws-lambda-java-serialization aws-lambda-java-events" DEP_LIST="${DEPS[$MODULE]}" @@ -137,23 +136,72 @@ jobs: echo "::notice::No intra-repo dependencies for $MODULE" fi - # No skip-tests option: never release an unverified artifact. - name: Run tests run: mvn verify --file "$MODULE/pom.xml" - # prepare/perform aren't atomic. Prepare locally (no push), publish, then - # push only after the artifact is live, so a failed publish never leaves an - # orphan tag on the remote. On failure, the rollback step cleans the runner. + - name: Configure AWS credentials (OIDC) + if: ${{ github.event.inputs.skip_publish != 'true' }} + uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ env.AWS_REGION }} + role-to-assume: ${{ env.OIDC_ROLE_ARN }} + role-session-name: GitHubActionsMavenCentralRelease + role-duration-seconds: 3600 + + - name: Fetch signing key and Sonatype credentials + if: ${{ github.event.inputs.skip_publish != 'true' }} + run: | + # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + + GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") + GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") + SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") + SONATYPE_PASSWORD=$(jq -r '."maven-central-password"' <<< "$CREDS_JSON") + echo "::add-mask::$GPG_PASSPHRASE" + echo "::add-mask::$SONATYPE_USERNAME" + echo "::add-mask::$SONATYPE_PASSWORD" + + # Import the key with loopback pinentry so Maven can sign non-interactively. + GNUPGHOME=$(mktemp -d) + chmod 700 "$GNUPGHOME" + echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" + echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" + export GNUPGHOME + gpgconf --kill gpg-agent || true + gpg --batch --import <<< "$GPG_PRIVATE_KEY" + GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') + + # settings.xml with the Sonatype token (server id "central"). + SETTINGS="$RUNNER_TEMP/settings.xml" + { + echo '' + echo "central" + echo "${SONATYPE_USERNAME}" + echo "${SONATYPE_PASSWORD}" + echo '' + } > "$SETTINGS" + + # Pass to later steps (values already masked). + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" + echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" + echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + + # prepare/perform aren't atomic: prepare locally, publish, push only after. - name: Release (prepare locally, publish, then push) if: ${{ github.event.inputs.skip_publish != 'true' }} run: | - # 1. Prepare locally (no push): create the release commits + tag. + # Prepare locally (no push): release commits + tag. mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" - # 2. Publish to Maven Central from the local tag. - mvn release:perform -DlocalCheckout=true --file "$MODULE/pom.xml" + # perform forks a fresh build, so pass settings/gpg via -Darguments. + mvn release:perform -DlocalCheckout=true \ + -Darguments="-s $MAVEN_SETTINGS -Prelease -Dgpg.keyname=$GPG_KEYNAME -Dgpg.passphrase=$GPG_PASSPHRASE" \ + --file "$MODULE/pom.xml" - # 3. Push commits and tag atomically (both or neither). + # Push commits + tag atomically, only after publish succeeded. git push --atomic origin \ "HEAD:${GITHUB_REF_NAME}" \ "refs/tags/${MODULE}-${EFFECTIVE_RELEASE_VERSION}" From 371508e543ebf1757d7796c1debed0b1a0fb1c54 Mon Sep 17 00:00:00 2001 From: Vipin Gupta Date: Tue, 21 Jul 2026 12:39:39 +0000 Subject: [PATCH 141/173] feat(runtime-interface-client): Add Lambda-Runtime-Invocation-Id support for cross-wiring protection Echo the invocation ID received from RAPID on /next back on /response and /error, enabling RAPID to detect and reject stale responses from timed-out invocations. Fully backward compatible. --- .../RELEASE.CHANGELOG.md | 4 + .../pom.xml | 2 +- .../lambda/runtime/api/client/AWSLambda.java | 4 +- .../runtimeapi/LambdaRuntimeApiClient.java | 6 +- .../LambdaRuntimeApiClientImpl.java | 20 +++-- .../api/client/runtimeapi/NativeClient.java | 2 +- .../runtimeapi/dto/InvocationRequest.java | 13 ++++ ...ime_api_client_runtimeapi_NativeClient.cpp | 18 ++++- ...ntime_api_client_runtimeapi_NativeClient.h | 2 +- .../include/aws/lambda-runtime/runtime.h | 12 ++- .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 33 ++++++-- .../runtime/api/client/AWSLambdaTest.java | 77 +++++++++++++++---- .../LambdaRuntimeApiClientImplTest.java | 34 ++++++-- .../codebuild-local/Dockerfile.agent | 4 +- 14 files changed, 182 insertions(+), 49 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md index 2391045fc..97d177034 100644 --- a/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### July 17, 2026 +`2.12.0` +- Add `Lambda-Runtime-Invocation-Id` header support for cross-wiring protection. The RIC now echoes the invocation ID received from RAPID on `/next` back on `/response` and `/error`, enabling RAPID to detect and reject stale responses from timed-out invocations. + ### May 13, 2026 `2.11.0` - Update aws-lambda-java-serialization dependency to 1.4.1 diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index fa543df00..ac62fd5b3 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.11.0 + 2.12.0 jar AWS Lambda Java Runtime Interface Client diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java index e5b221a80..b9aa0fd11 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambda.java @@ -315,7 +315,7 @@ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, try { ByteArrayOutputStream payload = lambdaRequestHandler.call(request); - runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray()); + runtimeClient.reportInvocationSuccess(request.getId(), payload.toByteArray(), request.getInvocationId()); // clear interrupted flag in case if it was set by user's code Thread.interrupted(); } catch (Throwable t) { @@ -323,7 +323,7 @@ private static void startRuntimeLoop(LambdaRequestHandler lambdaRequestHandler, userFault = UserFault.makeUserFault(t); shouldExit = exitLoopOnErrors && (t instanceof VirtualMachineError || t instanceof IOError || userFault.fatal); LambdaError error = createLambdaErrorFromThrowableOrUserFault(t); - runtimeClient.reportInvocationError(request.getId(), error); + runtimeClient.reportInvocationError(request.getId(), error, request.getInvocationId()); } finally { if (userFault != null) { lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED); diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java index a62aeb9b8..042bd2579 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClient.java @@ -34,15 +34,17 @@ public interface LambdaRuntimeApiClient { * Report invocation success * @param requestId request id * @param response byte array representing response + * @param invocationId invocation id for cross-wiring protection (may be null) */ - void reportInvocationSuccess(String requestId, byte[] response) throws IOException; + void reportInvocationSuccess(String requestId, byte[] response, String invocationId) throws IOException; /** * Report invocation error * @param requestId request id * @param error error to report + * @param invocationId invocation id for cross-wiring protection (may be null) */ - void reportInvocationError(String requestId, LambdaError error) throws IOException; + void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException; /** * SnapStart endpoint to report that beforeCheckoint hooks were executed diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java index caca69aa7..fce12eade 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImpl.java @@ -34,6 +34,7 @@ public class LambdaRuntimeApiClientImpl implements LambdaRuntimeApiClient { private static final String DEFAULT_CONTENT_TYPE = "application/json"; private static final String XRAY_ERROR_CAUSE_HEADER = "Lambda-Runtime-Function-XRay-Error-Cause"; private static final String ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type"; + private static final String INVOCATION_ID_HEADER = "Lambda-Runtime-Invocation-Id"; // 1MiB private static final int XRAY_ERROR_CAUSE_MAX_HEADER_SIZE = 1024 * 1024; @@ -55,7 +56,7 @@ public LambdaRuntimeApiClientImpl(String hostnameAndPort) { @Override public void reportInitError(LambdaError error) throws IOException { String endpoint = this.baseUrl + "/2018-06-01/runtime/init/error"; - reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE); + reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null); } @Override @@ -123,14 +124,15 @@ public InvocationRequest nextInvocationWithExponentialBackoff(LambdaContextLogge } @Override - public void reportInvocationSuccess(String requestId, byte[] response) { - NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response); + public void reportInvocationSuccess(String requestId, byte[] response, String invocationId) { + byte[] invocationIdBytes = invocationId != null ? invocationId.getBytes(UTF_8) : null; + NativeClient.postInvocationResponse(requestId.getBytes(UTF_8), response, invocationIdBytes); } @Override - public void reportInvocationError(String requestId, LambdaError error) throws IOException { + public void reportInvocationError(String requestId, LambdaError error, String invocationId) throws IOException { String endpoint = invocationEndpoint + requestId + "/error"; - reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE); + reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, invocationId); } @Override @@ -145,13 +147,17 @@ public void restoreNext() throws IOException { @Override public void reportRestoreError(LambdaError error) throws IOException { String endpoint = this.baseUrl + "/2018-06-01/runtime/restore/error"; - reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE); + reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE, null); } - void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize) throws IOException { + void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize, String invocationId) throws IOException { Map headers = new HashMap<>(); headers.put(ERROR_TYPE_HEADER, error.errorType.getRapidError()); + if (invocationId != null) { + headers.put(INVOCATION_ID_HEADER, invocationId); + } + if (error.xRayErrorCause != null) { byte[] xRayErrorCauseJson = DtoSerializers.serialize(error.xRayErrorCause); if (xRayErrorCauseJson != null && xRayErrorCauseJson.length < maxXrayHeaderSize) { diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java index 101aea4d0..5c690814b 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/NativeClient.java @@ -21,6 +21,6 @@ static void init(String awsLambdaRuntimeApi) { static native InvocationRequest next(); - static native void postInvocationResponse(byte[] requestId, byte[] response); + static native void postInvocationResponse(byte[] requestId, byte[] response, byte[] invocationId); } diff --git a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java index 656945b41..a589cb024 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java +++ b/aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/dto/InvocationRequest.java @@ -45,6 +45,11 @@ public class InvocationRequest { */ private String tenantId; + /** + * The invocation ID for cross-wiring protection. + */ + private String invocationId; + private byte[] content; public String getId() { @@ -107,6 +112,14 @@ public void setTenantId(String tenantId) { this.tenantId = tenantId; } + public String getInvocationId() { + return invocationId; + } + + public void setInvocationId(String invocationId) { + this.invocationId = invocationId; + } + public byte[] getContent() { return content; } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp index f06796616..fb6cd3ca3 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.cpp @@ -21,6 +21,7 @@ static jfieldID clientContextField; static jfieldID cognitoIdentityField; static jfieldID xrayTraceIdField; static jfieldID tenantIdField; +static jfieldID invocationIdField; jint JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -43,6 +44,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { clientContextField = env->GetFieldID(invocationRequestClass , "clientContext", "Ljava/lang/String;"); cognitoIdentityField = env->GetFieldID(invocationRequestClass , "cognitoIdentity", "Ljava/lang/String;"); tenantIdField = env->GetFieldID(invocationRequestClass, "tenantId", "Ljava/lang/String;"); + invocationIdField = env->GetFieldID(invocationRequestClass, "invocationId", "Ljava/lang/String;"); return JNI_VERSION; } @@ -112,6 +114,10 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_ CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, tenantIdField, env->NewStringUTF(response.tenant_id.c_str()))); } + if(response.invocation_id != ""){ + CHECK_EXCEPTION(env, env->SetObjectField(invocationRequest, invocationIdField, env->NewStringUTF(response.invocation_id.c_str()))); + } + bytes = reinterpret_cast(response.payload.c_str()); CHECK_EXCEPTION(env, jArray = env->NewByteArray(response.payload.length())); CHECK_EXCEPTION(env, env->SetByteArrayRegion(jArray, 0, response.payload.length(), bytes)); @@ -124,7 +130,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_ } JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse - (JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray) { + (JNIEnv *env, jobject thisObject, jbyteArray jrequestId, jbyteArray jresponseArray, jbyteArray jinvocationId) { std::string payload = toNativeString(env, jresponseArray); if ((env)->ExceptionOccurred()){ return; @@ -134,8 +140,16 @@ JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_run return; } + std::string invocationId; + if (jinvocationId != nullptr) { + invocationId = toNativeString(env, jinvocationId); + if ((env)->ExceptionOccurred()){ + return; + } + } + auto response = aws::lambda_runtime::invocation_response::success(payload, "application/json"); - auto outcome = CLIENT->post_success(requestId, response); + auto outcome = CLIENT->post_success(requestId, response, invocationId); if (!outcome.is_success()) { std::string errorMessage("Failed to post invocation response."); throwLambdaRuntimeClientException(env, errorMessage, outcome.get_failure()); diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h index 7219109b0..0f1aaa2ca 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient.h @@ -17,7 +17,7 @@ JNIEXPORT jobject JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_ (JNIEnv *, jobject); JNIEXPORT void JNICALL Java_com_amazonaws_services_lambda_runtime_api_client_runtimeapi_NativeClient_postInvocationResponse - (JNIEnv *, jobject, jbyteArray, jbyteArray); + (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray); #ifdef __cplusplus } diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h index c4868c1ba..96f90eaa2 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h @@ -66,6 +66,11 @@ struct invocation_request { */ std::string tenant_id; + /** + * Invocation ID for cross-wiring protection. + */ + std::string invocation_id; + /** * The number of milliseconds left before lambda terminates the current execution. */ @@ -154,11 +159,15 @@ class runtime { /** * Tells lambda that the function has succeeded. */ + post_outcome post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id); + post_outcome post_success(std::string const& request_id, invocation_response const& handler_response); /** * Tells lambda that the function has failed. */ + post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id); + post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); private: @@ -167,7 +176,8 @@ class runtime { post_outcome do_post( std::string const& url, std::string const& request_id, - invocation_response const& handler_response); + invocation_response const& handler_response, + std::string const& invocation_id); private: std::string const m_user_agent_header; diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp index 84a84b439..2d86a1d7e 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp @@ -41,6 +41,7 @@ static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; +static constexpr auto INVOCATION_ID_HEADER = "lambda-runtime-invocation-id"; thread_local static CURL* m_curl_handle = curl_easy_init(); enum Endpoints { @@ -306,25 +307,40 @@ runtime::next_outcome runtime::get_next() if (resp.has_header(TENANT_ID_HEADER)) { req.tenant_id = resp.get_header(TENANT_ID_HEADER); } + + if (resp.has_header(INVOCATION_ID_HEADER)) { + req.invocation_id = resp.get_header(INVOCATION_ID_HEADER); + } return next_outcome(req); } -runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) +runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id) { std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response"; - return do_post(url, request_id, handler_response); + return do_post(url, request_id, handler_response, invocation_id); } -runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response) +runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) +{ + return post_success(request_id, handler_response, ""); +} + +runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id) { std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error"; - return do_post(url, request_id, handler_response); + return do_post(url, request_id, handler_response, invocation_id); +} + +runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response) +{ + return post_failure(request_id, handler_response, ""); } runtime::post_outcome runtime::do_post( std::string const& url, std::string const& request_id, - invocation_response const& handler_response) + invocation_response const& handler_response, + std::string const& invocation_id) { set_curl_post_result_options(); curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); @@ -341,6 +357,9 @@ runtime::post_outcome runtime::do_post( headers = curl_slist_append(headers, "Expect:"); headers = curl_slist_append(headers, "transfer-encoding:"); headers = curl_slist_append(headers, m_user_agent_header.c_str()); + if (!invocation_id.empty()) { + headers = curl_slist_append(headers, (std::string(INVOCATION_ID_HEADER) + ": " + invocation_id).c_str()); + } auto const& payload = handler_response.get_payload(); logging::log_debug( LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); @@ -436,13 +455,13 @@ void run_handler(std::function c logging::log_info(LOG_TAG, "Invoking user handler completed."); if (res.is_success()) { - const auto post_outcome = rt.post_success(req.request_id, res); + const auto post_outcome = rt.post_success(req.request_id, res, req.invocation_id); if (!handle_post_outcome(post_outcome, req.request_id)) { return; // TODO: implement a better retry strategy } } else { - const auto post_outcome = rt.post_failure(req.request_id, res); + const auto post_outcome = rt.post_failure(req.request_id, res, req.invocation_id); if (!handle_post_outcome(post_outcome, req.request_id)) { return; // TODO: implement a better retry strategy } diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java index 49b59c2cd..100465531 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/AWSLambdaTest.java @@ -242,7 +242,7 @@ void testConcurrentRunWithPlatformThreads() throws Throwable { AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); // Success Reports Must Equal number of tasks that ran successfully. - verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(7)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any()); // Hashmap keys should equal the number of threads (runtime loops). assertEquals(4, SampleHandler.hashMap.size()); // Hashmap total count should equal all tasks that ran * number of iterations per task @@ -280,11 +280,11 @@ void testConcurrentRunWithPlatformThreadsWithFailures() throws Throwable { verify(lambdaLogger, times(6)).log(anyString(), eq(LogLevel.ERROR)); // Failed invokes should be reported. - verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); - verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any()); // Success Reports Must Equal number of tasks that ran successfully. - verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any()); // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). assertEquals(2, SampleHandler.hashMap.size()); @@ -326,12 +326,12 @@ void testConcurrentModeLoopDoesNotExitExceptForLambdaRuntimeClientMaxRetriesExce verify(lambdaLogger, times(4)).log(anyString(), eq(LogLevel.ERROR)); // Failed invokes should be reported. - verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); - verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); - verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any(), any()); // Success Reports Must Equal number of tasks that ran successfully. - verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any()); // Hashmap keys should equal the minumum between(number of threads (runtime loops) AND number of tasks that ran successfully). assertEquals(1, SampleHandler.hashMap.size()); @@ -516,12 +516,12 @@ void testSequentialWithFatalUserFaultErrorStopsLoop() throws Throwable { verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); // Failed invokes should be reported. - verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); - verify(runtimeClient).reportInvocationError(eq(UserFaultID), any()); + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any()); + verify(runtimeClient).reportInvocationError(eq(UserFaultID), any(), any()); // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and userFaultRequest. - verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); - verify(runtimeClient, times(2)).reportInvocationError(any(), any()); + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any(), any()); // Hashmap keys should equal one as it is not multithreaded. assertEquals(1, SampleHandler.hashMap.size()); @@ -562,12 +562,12 @@ void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable { verify(lambdaLogger, times(2)).log(anyString(), eq(LogLevel.ERROR)); // Failed invokes should be reported. - verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any()); - verify(runtimeClient).reportInvocationError(eq(IOErrorID), any()); + verify(runtimeClient).reportInvocationError(eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), any()); + verify(runtimeClient).reportInvocationError(eq(IOErrorID), any(), any()); // Success Reports Must Equal number of tasks that ran successfully. And only 2 Error reports for failImmediatelyRequest and virtualMachineErrorRequest. - verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any()); - verify(runtimeClient, times(2)).reportInvocationError(any(), any()); + verify(runtimeClient, times(2)).reportInvocationSuccess(eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), any()); + verify(runtimeClient, times(2)).reportInvocationError(any(), any(), any()); // Hashmap keys should equal one as it is not multithreaded. assertEquals(1, SampleHandler.hashMap.size()); @@ -575,4 +575,49 @@ void testSequentialWithVirtualMachineErrorStopsLoop() throws Throwable { // Hashmap total count should equal all tasks that ran * number of iterations per task assertEquals(2 * SampleHandler.nOfIterations, SampleHandler.globalCounter.get()); } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testInvocationIdIsPassedToReportSuccess() throws Throwable { + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest requestWithInvId = getFakeInvocationRequest(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE); + requestWithInvId.setInvocationId("test-inv-uuid-1234"); + + // Fatal error to stop the loop after one successful invocation + InvocationRequest fatalRequest = mock(InvocationRequest.class); + when(fatalRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn("fatal"); + + when(runtimeClient.nextInvocation()) + .thenReturn(requestWithInvId) + .thenReturn(fatalRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + verify(runtimeClient).reportInvocationSuccess( + eq(SampleHandler.ADD_ENTRY_TO_MAP_ID_OP_MODE), any(), eq("test-inv-uuid-1234")); + } + + @Test + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void testInvocationIdIsPassedToReportError() throws Throwable { + when(lambdaLogger.getLogFormat()).thenReturn(LogFormat.JSON); + when(concurrencyConfig.isMultiConcurrent()).thenReturn(false); + + InvocationRequest requestWithInvId = getFakeInvocationRequest(SampleHandler.FAIL_IMMEDIATELY_OP_MODE); + requestWithInvId.setInvocationId("test-inv-uuid-5678"); + + // Fatal error to stop the loop after one error invocation + InvocationRequest fatalRequest = mock(InvocationRequest.class); + when(fatalRequest.getId()).thenThrow(UserFault.makeUserFault(new IOError(new Throwable()), true)).thenReturn("fatal"); + + when(runtimeClient.nextInvocation()) + .thenReturn(requestWithInvId) + .thenReturn(fatalRequest); + + AWSLambda.startRuntimeLoops(lambdaRequestHandler, lambdaLogger, concurrencyConfig, runtimeClient); + + verify(runtimeClient).reportInvocationError( + eq(SampleHandler.FAIL_IMMEDIATELY_OP_MODE), any(), eq("test-inv-uuid-5678")); + } } \ No newline at end of file diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java index 710c1565e..9d5929263 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/runtimeapi/LambdaRuntimeApiClientImplTest.java @@ -244,7 +244,7 @@ public void reportInvocationErrorTest() { mockWebServer.enqueue(mockResponse); LambdaError lambdaError = new LambdaError(errorRequest, rapidErrorType); - lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError); + lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError, null); RecordedRequest recordedRequest = mockWebServer.takeRequest(); HttpUrl actualUrl = recordedRequest.getRequestUrl(); String expectedUrl = "http://" + getHostnamePort() + "/2018-06-01/runtime/invocation/1234/error"; @@ -274,7 +274,7 @@ public void reportInvocationErrorTestWrongStatusCode() { mockWebServer.enqueue(mockResponse); LambdaError lambdaError = new LambdaError(errorRequest, rapidErrorType); - lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError); + lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError, null); fail(); } catch(LambdaRuntimeClientException e) { String expectedUrl = "http://" + getHostnamePort() + "/2018-06-01/runtime/invocation/1234/error"; @@ -318,7 +318,7 @@ public void reportLambdaErrorWithXRayTest() { XRayErrorCause xRayErrorCause = new XRayErrorCause(workingDirectory, exceptions, paths); LambdaError lambdaError = new LambdaError(errorRequest, xRayErrorCause, rapidErrorType); - lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError); + lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError, null); RecordedRequest recordedRequest = mockWebServer.takeRequest(); String xrayErrorCauseHeader = recordedRequest.getHeader("Lambda-Runtime-Function-XRay-Error-Cause"); @@ -339,7 +339,7 @@ public void reportInvocationSuccessTest() { mockWebServer.enqueue(mockResponse); String response = "{\"msg\":\"test\"}"; - lambdaRuntimeApiClientImpl.reportInvocationSuccess(requestId, response.getBytes()); + lambdaRuntimeApiClientImpl.reportInvocationSuccess(requestId, response.getBytes(), null); RecordedRequest recordedRequest = mockWebServer.takeRequest(); HttpUrl actualUrl = recordedRequest.getRequestUrl(); String expectedUrl = "http://" + getHostnamePort() + "/2018-06-01/runtime/invocation/1234/response"; @@ -456,7 +456,7 @@ public void createUrlMalformedTest() { RapidErrorType rapidErrorType = RapidErrorType.AfterRestoreError; LambdaError lambdaError = new LambdaError(errorRequest, rapidErrorType); RuntimeException thrown = assertThrows(RuntimeException.class, ()->{ - lambdaRuntimeApiClientImpl.reportLambdaError("invalidurl", lambdaError, 100); + lambdaRuntimeApiClientImpl.reportLambdaError("invalidurl", lambdaError, 100, null); }); assertTrue(thrown.getLocalizedMessage().contains("java.net.MalformedURLException")); } @@ -483,7 +483,7 @@ public void lambdaReportErrorXRayHeaderTooLongTest() { XRayErrorCause xRayErrorCause = new XRayErrorCause(workingDirectory, exceptions, paths); LambdaError lambdaError = new LambdaError(errorRequest, xRayErrorCause, rapidErrorType); - lambdaRuntimeApiClientImpl.reportLambdaError("http://" + getHostnamePort(), lambdaError, 10); + lambdaRuntimeApiClientImpl.reportLambdaError("http://" + getHostnamePort(), lambdaError, 10, null); RecordedRequest recordedRequest = mockWebServer.takeRequest(); String xrayErrorCauseHeader = recordedRequest.getHeader("Lambda-Runtime-Function-XRay-Error-Cause"); @@ -511,6 +511,28 @@ private void verifyNextInvocationRequest() throws Exception { assertEquals("", actualBody); } + @Test + public void reportInvocationErrorWithInvocationIdTest() { + try { + RapidErrorType rapidErrorType = RapidErrorType.AfterRestoreError; + + MockResponse mockResponse = new MockResponse(); + mockResponse.setResponseCode(HTTP_ACCEPTED); + mockWebServer.enqueue(mockResponse); + + String invocationId = "test-invocation-uuid-1234"; + LambdaError lambdaError = new LambdaError(errorRequest, rapidErrorType); + lambdaRuntimeApiClientImpl.reportInvocationError(requestId, lambdaError, invocationId); + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + + String invocationIdHeader = recordedRequest.getHeader("Lambda-Runtime-Invocation-Id"); + assertEquals(invocationId, invocationIdHeader); + } catch(Exception e) { + e.printStackTrace(); + fail(); + } + } + private String getHostnamePort() { return mockWebServer.getHostName() + ":" + mockWebServer.getPort(); } diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent index 350305e81..2b1ba299e 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent @@ -1,9 +1,7 @@ FROM public.ecr.aws/amazoncorretto/amazoncorretto:8 # Install docker and buildx extension -RUN amazon-linux-extras enable docker && \ - yum clean metadata && \ - yum install -y docker tar gzip unzip file +RUN yum install -y docker tar gzip unzip file findutils COPY --from=docker/buildx-bin:latest /buildx /usr/libexec/docker/cli-plugins/docker-buildx From f00150c1ef60696ee02041afbbd808b842a3bfbd Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 28 Jul 2026 15:58:21 +0100 Subject: [PATCH 142/173] refactor(release): reusable CI actions and review hardening - Extract resolve-release-version and configure-release-aws-credentials composite actions shared by both release workflows - Fetch signing key + Sonatype token inside the publish step so secrets never cross $GITHUB_ENV; scrub settings.xml and keyring on exit - Guard releases to the main branch; cut OIDC role session to 300s - Set RIC to 2.12.0-SNAPSHOT so the release pipeline can publish it --- .../action.yml | 27 ++++++ .../resolve-release-version/action.yml | 54 +++++++++++ .../release-runtime-interface-client.yml | 83 ++++++++--------- .github/workflows/release.yml | 93 +++++++++---------- .../pom.xml | 2 +- 5 files changed, 166 insertions(+), 93 deletions(-) create mode 100644 .github/actions/configure-release-aws-credentials/action.yml create mode 100644 .github/actions/resolve-release-version/action.yml diff --git a/.github/actions/configure-release-aws-credentials/action.yml b/.github/actions/configure-release-aws-credentials/action.yml new file mode 100644 index 000000000..a02a1e9a4 --- /dev/null +++ b/.github/actions/configure-release-aws-credentials/action.yml @@ -0,0 +1,27 @@ +name: "Configure AWS credentials for release (OIDC)" +description: > + Assumes the release OIDC role via aws-actions/configure-aws-credentials so the + job can read the signing key and Sonatype token from Secrets Manager. Pinning + of the underlying action lives here so it is updated in one place. + +inputs: + aws-region: + description: "AWS region to operate in." + required: true + role-to-assume: + description: "ARN of the OIDC role to assume." + required: true + role-session-name: + description: "Session name for the assumed role (helps distinguish callers in CloudTrail)." + required: true + +runs: + using: composite + steps: + - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + with: + aws-region: ${{ inputs.aws-region }} + role-to-assume: ${{ inputs.role-to-assume }} + role-session-name: ${{ inputs.role-session-name }} + # Short-lived: the job only needs the role briefly to read two secrets. + role-duration-seconds: 300 diff --git a/.github/actions/resolve-release-version/action.yml b/.github/actions/resolve-release-version/action.yml new file mode 100644 index 000000000..06d3f4f1e --- /dev/null +++ b/.github/actions/resolve-release-version/action.yml @@ -0,0 +1,54 @@ +name: "Resolve and validate release version" +description: > + Reads the module POM version (the source of truth), verifies it is a + -SNAPSHOT, and derives the effective release version (the optional override, + or the POM version with -SNAPSHOT stripped). Exports CURRENT_VERSION and + EFFECTIVE_RELEASE_VERSION to the job environment for subsequent steps. + +inputs: + module: + description: "Module directory containing the pom.xml to release." + required: true + release-version-override: + description: "Optional release version; defaults to the POM version without -SNAPSHOT." + required: false + default: "" + validate-module-dir: + description: "Fail if the module directory or its pom.xml is missing (use for the choice-driven workflow)." + required: false + default: "false" + +runs: + using: composite + steps: + - name: Resolve and validate release version + shell: bash + env: + MODULE: ${{ inputs.module }} + RELEASE_VERSION_OVERRIDE: ${{ inputs.release-version-override }} + VALIDATE_MODULE_DIR: ${{ inputs.validate-module-dir }} + run: | + if [[ "$VALIDATE_MODULE_DIR" == "true" ]]; then + if [[ ! -d "$MODULE" ]]; then + echo "::error::Module directory '$MODULE' does not exist" + exit 1 + fi + if [[ ! -f "$MODULE/pom.xml" ]]; then + echo "::error::No pom.xml found in '$MODULE'" + exit 1 + fi + fi + + # The POM version is the source of truth and must be a SNAPSHOT. + CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" + if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then + echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" + exit 1 + fi + + # Optional override; default strips -SNAPSHOT. + EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_OVERRIDE:-${CURRENT_VERSION%-SNAPSHOT}}" + + echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" + echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 43090d1b7..6cc3bd6ec 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -57,6 +57,16 @@ jobs: runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: + # Manual (workflow_dispatch) releases must only run from main, never from + # an arbitrary branch that could carry unreviewed release logic. Guarding + # the first job blocks the whole pipeline (release needs build-natives). + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up JDK 8 @@ -66,15 +76,11 @@ jobs: distribution: corretto cache: maven - - name: Resolve release version - run: | - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - echo "EFFECTIVE_RELEASE_VERSION=${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" >> "$GITHUB_ENV" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} # -DskipTests: only installed so the module compiles, not released here. - name: Install intra-repo dependencies @@ -124,17 +130,14 @@ jobs: distribution: corretto cache: maven - - name: Validate inputs and resolve versions - run: | - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - - EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + - name: Resolve next development version and tag + run: | # Next development version: use the override, or bump the patch. if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then if [[ "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then @@ -147,7 +150,6 @@ jobs: NEXT_DEV_VERSION="${MA}.${MI}.$((PA + 1))-SNAPSHOT" fi - echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" echo "NEXT_DEV_VERSION=$NEXT_DEV_VERSION" >> "$GITHUB_ENV" echo "TAG_NAME=${MODULE}-${EFFECTIVE_RELEASE_VERSION}" >> "$GITHUB_ENV" echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (next dev $NEXT_DEV_VERSION)" @@ -190,20 +192,32 @@ jobs: - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + uses: ./.github/actions/configure-release-aws-credentials with: aws-region: ${{ env.AWS_REGION }} role-to-assume: ${{ env.OIDC_ROLE_ARN }} role-session-name: GitHubActionsRicMavenCentralRelease - role-duration-seconds: 3600 - - name: Fetch signing key and Sonatype credentials + # Fetch signing material and publish in a single step so the GPG passphrase + # and Sonatype token stay in this shell and never cross a $GITHUB_ENV + # boundary, where a later (possibly compromised) step could read them. + # -DmultiArch=false builds only the host .so; the aarch_64 .so is already + # staged, so the main JAR still bundles all four. build-helper attaches + # the staged classifier JARs. Gate already ran, so -DskipTests. + - name: Publish to Maven Central if: ${{ github.event.inputs.skip_publish != 'true' }} + env: + IS_JAVA_8: true run: | - # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + # Scrub the settings.xml (contains the Sonatype token) and the keyring + # on exit, so no sensitive file is left on the runner even on failure. + MAVEN_SETTINGS="$RUNNER_TEMP/settings.xml" + export GNUPGHOME=$(mktemp -d) + trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT + + # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) - GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") @@ -213,38 +227,23 @@ jobs: echo "::add-mask::$SONATYPE_PASSWORD" # Import the key with loopback pinentry so Maven can sign non-interactively. - GNUPGHOME=$(mktemp -d) chmod 700 "$GNUPGHOME" echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" - export GNUPGHOME gpgconf --kill gpg-agent || true gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') # settings.xml with the Sonatype token (server id "central"). - SETTINGS="$RUNNER_TEMP/settings.xml" { echo '' echo "central" echo "${SONATYPE_USERNAME}" echo "${SONATYPE_PASSWORD}" echo '' - } > "$SETTINGS" + } > "$MAVEN_SETTINGS" - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" - echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" - echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" - echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" - - # -DmultiArch=false builds only the host .so; the aarch_64 .so is already - # staged, so the main JAR still bundles all four. build-helper attaches - # the staged classifier JARs. Gate already ran, so -DskipTests. - - name: Publish to Maven Central - if: ${{ github.event.inputs.skip_publish != 'true' }} - env: - IS_JAVA_8: true - run: | + # --- Publish --- mvn deploy -Prelease -DskipTests -DmultiArch=false \ -s "$MAVEN_SETTINGS" \ -Dgpg.keyname="$GPG_KEYNAME" -Dgpg.passphrase="$GPG_PASSPHRASE" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e148c0131..d9437b776 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,15 @@ jobs: timeout-minutes: 30 steps: + # Manual (workflow_dispatch) releases must only run from main, never from + # an arbitrary branch that could carry unreviewed release logic. + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 # full history for tagging/pushing @@ -71,41 +80,19 @@ jobs: distribution: corretto cache: maven - - name: Validate inputs and resolve versions - run: | - if [[ ! -d "$MODULE" ]]; then - echo "::error::Module directory '$MODULE' does not exist" - exit 1 - fi - if [[ ! -f "$MODULE/pom.xml" ]]; then - echo "::error::No pom.xml found in '$MODULE'" - exit 1 - fi - - # The POM version is the source of truth and must be a SNAPSHOT. - CURRENT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") - CURRENT_VERSION="${CURRENT_VERSION//[$'\r\n']/}" - if [[ "$CURRENT_VERSION" != *-SNAPSHOT ]]; then - echo "::error::POM version '$CURRENT_VERSION' is not a SNAPSHOT" - exit 1 - fi - - # releaseVersion input is an optional override; default strips -SNAPSHOT. - EFFECTIVE_RELEASE_VERSION="${RELEASE_VERSION_INPUT:-${CURRENT_VERSION%-SNAPSHOT}}" + - name: Resolve and validate release version + uses: ./.github/actions/resolve-release-version + with: + module: ${{ env.MODULE }} + release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + validate-module-dir: "true" + - name: Validate development version override + run: | if [[ -n "$DEVELOPMENT_VERSION_INPUT" && "$DEVELOPMENT_VERSION_INPUT" != *-SNAPSHOT ]]; then echo "::error::developmentVersion '$DEVELOPMENT_VERSION_INPUT' must end with -SNAPSHOT" exit 1 fi - - # Build the release plugin version args once; reused by both paths. - RELEASE_ARGS="-DreleaseVersion=$EFFECTIVE_RELEASE_VERSION" - if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then - RELEASE_ARGS="$RELEASE_ARGS -DdevelopmentVersion=$DEVELOPMENT_VERSION_INPUT" - fi - - echo "EFFECTIVE_RELEASE_VERSION=$EFFECTIVE_RELEASE_VERSION" >> "$GITHUB_ENV" - echo "RELEASE_ARGS=$RELEASE_ARGS" >> "$GITHUB_ENV" echo "::notice::Releasing $MODULE $EFFECTIVE_RELEASE_VERSION (POM currently $CURRENT_VERSION)" - name: Configure git user @@ -141,20 +128,28 @@ jobs: - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4 + uses: ./.github/actions/configure-release-aws-credentials with: aws-region: ${{ env.AWS_REGION }} role-to-assume: ${{ env.OIDC_ROLE_ARN }} role-session-name: GitHubActionsMavenCentralRelease - role-duration-seconds: 3600 - - name: Fetch signing key and Sonatype credentials + # Fetch signing material and publish in a single step so the GPG passphrase + # and Sonatype token stay in this shell and never cross a $GITHUB_ENV + # boundary, where a later (possibly compromised) step could read them. + # prepare/perform aren't atomic: prepare locally, publish, push only after. + - name: Release (prepare locally, publish, then push) if: ${{ github.event.inputs.skip_publish != 'true' }} run: | - # Shared secrets from LambdaMavenDeploy; nothing stored in GitHub. + # Scrub the settings.xml (contains the Sonatype token) and the keyring + # on exit, so no sensitive file is left on the runner even on failure. + MAVEN_SETTINGS="$RUNNER_TEMP/settings.xml" + export GNUPGHOME=$(mktemp -d) + trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT + + # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) - GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") @@ -164,37 +159,31 @@ jobs: echo "::add-mask::$SONATYPE_PASSWORD" # Import the key with loopback pinentry so Maven can sign non-interactively. - GNUPGHOME=$(mktemp -d) chmod 700 "$GNUPGHOME" echo "allow-loopback-pinentry" > "$GNUPGHOME/gpg-agent.conf" echo "pinentry-mode loopback" > "$GNUPGHOME/gpg.conf" - export GNUPGHOME gpgconf --kill gpg-agent || true gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') # settings.xml with the Sonatype token (server id "central"). - SETTINGS="$RUNNER_TEMP/settings.xml" { echo '' echo "central" echo "${SONATYPE_USERNAME}" echo "${SONATYPE_PASSWORD}" echo '' - } > "$SETTINGS" + } > "$MAVEN_SETTINGS" - # Pass to later steps (values already masked). - echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" - echo "GPG_KEYNAME=$GPG_KEYNAME" >> "$GITHUB_ENV" - echo "GPG_PASSPHRASE=$GPG_PASSPHRASE" >> "$GITHUB_ENV" - echo "MAVEN_SETTINGS=$SETTINGS" >> "$GITHUB_ENV" + # --- Release: build args as an array so each value is a single, + # properly quoted argument (no word-splitting of untrusted input). --- + RELEASE_ARGS=(-DreleaseVersion="$EFFECTIVE_RELEASE_VERSION") + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS+=(-DdevelopmentVersion="$DEVELOPMENT_VERSION_INPUT") + fi - # prepare/perform aren't atomic: prepare locally, publish, push only after. - - name: Release (prepare locally, publish, then push) - if: ${{ github.event.inputs.skip_publish != 'true' }} - run: | # Prepare locally (no push): release commits + tag. - mvn release:prepare -DpushChanges=false $RELEASE_ARGS --file "$MODULE/pom.xml" + mvn release:prepare -DpushChanges=false "${RELEASE_ARGS[@]}" --file "$MODULE/pom.xml" # perform forks a fresh build, so pass settings/gpg via -Darguments. mvn release:perform -DlocalCheckout=true \ @@ -209,7 +198,11 @@ jobs: - name: Dry-run release (prepare only, no publish) if: ${{ github.event.inputs.skip_publish == 'true' }} run: | - mvn release:prepare -DdryRun=true $RELEASE_ARGS --file "$MODULE/pom.xml" + RELEASE_ARGS=(-DreleaseVersion="$EFFECTIVE_RELEASE_VERSION") + if [[ -n "$DEVELOPMENT_VERSION_INPUT" ]]; then + RELEASE_ARGS+=(-DdevelopmentVersion="$DEVELOPMENT_VERSION_INPUT") + fi + mvn release:prepare -DdryRun=true "${RELEASE_ARGS[@]}" --file "$MODULE/pom.xml" mvn release:clean --file "$MODULE/pom.xml" || true # Nothing was pushed, so this only cleans the runner for a retry. diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 10a755c8c..a9c49c958 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.12.0 + 2.12.0-SNAPSHOT jar AWS Lambda Java Runtime Interface Client From b9418bc3e02c03fd149e4174dcc3d46ef643773d Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 28 Jul 2026 23:00:17 +0100 Subject: [PATCH 143/173] feat(release): gate publish on module-specific tests - log4j2 gates on the CloudWatch integration test (reusable workflow), bound to the publish event - serialization gates on the aws-lambda-java-tests suite (version-overridden), which its own mvn verify does not run --- .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9437b776..2305ade7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,20 @@ env: OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} jobs: + # Pre-publish gate for log4j2: deploy a real Lambda, invoke it, + # and assert the log line reaches CloudWatch. Binds the end-to-end validation + # to the publish event itself. Skipped for every other module, which are + # covered by their own tests (or the cross-module gate below). + integration-test: + if: ${{ github.event.inputs.module == 'aws-lambda-java-log4j2' }} + uses: ./.github/workflows/run-integration-test.yml + secrets: inherit + release: + needs: [integration-test] + # Publish when the gate passed, or when it was skipped for a non-log4j2 + # module. A failed or cancelled gate blocks the release. + if: ${{ always() && (needs.integration-test.result == 'success' || needs.integration-test.result == 'skipped') }} runs-on: ubuntu-latest environment: Release timeout-minutes: 30 @@ -126,6 +139,28 @@ jobs: - name: Run tests run: mvn verify --file "$MODULE/pom.xml" + # Cross-module gate: serialization has no tests in its own build, so the + # `mvn verify` above exercises nothing. Its behavioral coverage lives in + # aws-lambda-java-tests, which depends on serialization via a version + # property. Install the just-built serialization and run that suite + # against it, so we never publish serialization the suite hasn't exercised. + - name: Run cross-module test gate + run: | + case "$MODULE" in + aws-lambda-java-serialization) + MOD_VER=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version --file "$MODULE/pom.xml") + MOD_VER="${MOD_VER//[$'\r\n']/}" + echo "::group::Installing $MODULE $MOD_VER for the gate" + mvn install -DskipTests --file "$MODULE/pom.xml" + echo "::endgroup::" + echo "::notice::Gating $MODULE on aws-lambda-java-tests (aws-lambda-java-serialization.version=$MOD_VER)" + mvn verify -Daws-lambda-java-serialization.version="$MOD_VER" --file aws-lambda-java-tests/pom.xml + ;; + *) + echo "::notice::No cross-module test gate for $MODULE" + ;; + esac + - name: Configure AWS credentials (OIDC) if: ${{ github.event.inputs.skip_publish != 'true' }} uses: ./.github/actions/configure-release-aws-credentials From 8fbec4a175c148f4a736a4a262d890359bd52239 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Thu, 30 Jul 2026 15:39:46 +0100 Subject: [PATCH 144/173] fix(ci): add workflow_call trigger to integration test release.yml invokes run-integration-test.yml as a reusable workflow via uses:, which GitHub requires the called workflow to declare with an on: workflow_call trigger. Without it the log4j2 release would fail when resolving the reusable workflow. --- .github/workflows/run-integration-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index ad46d7fd6..35456a7c5 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -9,6 +9,7 @@ permissions: on: workflow_dispatch: + workflow_call: push: branches: [ main ] paths: From 12e36128374a747c697757083665587625b3d60b Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Tue, 11 Aug 2026 14:25:14 +0100 Subject: [PATCH 145/173] ci: update release signing key source and native build runners Point the Maven Central release workflows at the dedicated GPG signing key secret, and run the RIC native builds on the self-hosted release runners instead of the default hosted runners. --- .github/workflows/release-runtime-interface-client.yml | 6 +++--- .github/workflows/release.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 6cc3bd6ec..90858d0f9 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -49,10 +49,10 @@ jobs: matrix: include: - arch: x86_64 - runner: ubuntu-latest + runner: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} profiles: linux-x86_64 linux_musl-x86_64 - arch: aarch64 - runner: ubuntu-24.04-arm + runner: codebuild-aws-lambda-java-libs-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} profiles: linux-aarch64 linux_musl-aarch64 runs-on: ${{ matrix.runner }} timeout-minutes: 45 @@ -216,7 +216,7 @@ jobs: trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- - GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2305ade7c..a08d3b74a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -183,7 +183,7 @@ jobs: trap 'rm -rf "$MAVEN_SETTINGS" "$GNUPGHOME"' EXIT # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- - GPG_JSON=$(aws secretsmanager get-secret-value --secret-id maven.gpg.keys --query SecretString --output text) + GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --query SecretString --output text) CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") From a6f23d841826051dff3af0e24d7a41ea05dd3a0c Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 12 Aug 2026 11:04:21 +0100 Subject: [PATCH 146/173] ci: point release credentials at dedicated secret Read the Sonatype credentials from the dedicated per-project secret instead of the shared one, matching the new signing key source. --- .github/workflows/release-runtime-interface-client.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 90858d0f9..7865a96f4 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -217,7 +217,7 @@ jobs: # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --query SecretString --output text) - CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/maven-sonatype-creds --query SecretString --output text) GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a08d3b74a..500a2c021 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -184,7 +184,7 @@ jobs: # --- Signing key + Sonatype token (shared secrets from LambdaMavenDeploy) --- GPG_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/gpg-signing-key --query SecretString --output text) - CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id maven.sonatype.creds --query SecretString --output text) + CREDS_JSON=$(aws secretsmanager get-secret-value --secret-id lambda-runtimes/java/maven-sonatype-creds --query SecretString --output text) GPG_PRIVATE_KEY=$(jq -r '.private' <<< "$GPG_JSON") GPG_PASSPHRASE=$(jq -r '.passphrase' <<< "$GPG_JSON") SONATYPE_USERNAME=$(jq -r '."maven-central-login"' <<< "$CREDS_JSON") From 1dcf590a246ce37363648c5760317083ef34a25b Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Wed, 12 Aug 2026 21:38:50 +0100 Subject: [PATCH 147/173] ci: run release jobs on CodeBuild runners --- .github/workflows/release-runtime-interface-client.yml | 10 +++++----- .github/workflows/release.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 7865a96f4..06cc80930 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -1,9 +1,9 @@ name: Release RIC to Maven Central # RIC ships a native JNI lib for 4 targets + a main JAR (5 artifacts). Each -# native lib is built on its own architecture (x86_64 on ubuntu-latest, -# aarch_64 on ubuntu-24.04-arm) instead of emulating with QEMU. A build matrix -# produces the classifier JARs, then one job assembles and publishes them. +# native lib is built on its own architecture (x86_64 and aarch64 CodeBuild +# runners) instead of emulating with QEMU. A build matrix produces the +# classifier JARs, then one job assembles and publishes them. on: workflow_dispatch: @@ -115,7 +115,7 @@ jobs: # Assemble all native builds and publish. release: needs: build-natives - runs-on: ubuntu-latest + runs-on: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} environment: Release timeout-minutes: 30 steps: @@ -283,5 +283,5 @@ jobs: echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY echo "| Artifacts | main JAR + linux/linux_musl x x86_64/aarch_64 classifier JARs |" >> $GITHUB_STEP_SUMMARY - echo "| Built natively | x86_64 on ubuntu-latest, aarch_64 on ubuntu-24.04-arm (no QEMU) |" >> $GITHUB_STEP_SUMMARY + echo "| Built natively | x86_64 and aarch_64 on CodeBuild runners (no QEMU) |" >> $GITHUB_STEP_SUMMARY echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 500a2c021..57ec1f3a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,7 +67,7 @@ jobs: # Publish when the gate passed, or when it was skipped for a non-log4j2 # module. A failed or cancelled gate blocks the release. if: ${{ always() && (needs.integration-test.result == 'success' || needs.integration-test.result == 'skipped') }} - runs-on: ubuntu-latest + runs-on: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} environment: Release timeout-minutes: 30 From 70d79913b267104cab08f2ce2ff232b1d56ebde3 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Thu, 13 Aug 2026 13:43:18 +0100 Subject: [PATCH 148/173] ci(release): configure Maven CodeArtifact mirror --- .../actions/configure-maven-mirror/action.yml | 42 +++++++++++++ .../release-runtime-interface-client.yml | 61 ++++++++++++++----- .github/workflows/release.yml | 43 ++++++++++--- 3 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 .github/actions/configure-maven-mirror/action.yml diff --git a/.github/actions/configure-maven-mirror/action.yml b/.github/actions/configure-maven-mirror/action.yml new file mode 100644 index 000000000..ce309a8f7 --- /dev/null +++ b/.github/actions/configure-maven-mirror/action.yml @@ -0,0 +1,42 @@ +name: Configure Maven CodeArtifact mirror +description: Configure Maven to resolve dependencies through the release CodeArtifact repository. + +runs: + using: composite + steps: + - shell: bash + run: | + CA_DOMAIN=aws-lambda + CA_REPO=maven-central-store + + # Uses the ambient region and caller account. + TOKEN=$(aws codeartifact get-authorization-token \ + --domain "$CA_DOMAIN" --query authorizationToken --output text) + echo "::add-mask::$TOKEN" + + CA_URL=$(aws codeartifact get-repository-endpoint \ + --domain "$CA_DOMAIN" --repository "$CA_REPO" --format maven \ + --query repositoryEndpoint --output text) + + # * routes all resolution through the mirror; + # deployment uses distributionManagement and is unaffected. + mkdir -p "$HOME/.m2" + cat > "$HOME/.m2/settings.xml" < + + + codeartifact-mirror + aws + ${TOKEN} + + + + + codeartifact-mirror + release CodeArtifact Maven Central proxy + ${CA_URL} + * + + + + EOF diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 06cc80930..772b12cae 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -69,12 +69,22 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up JDK 8 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 - with: - java-version: 8 - distribution: corretto - cache: maven + # Use the CodeBuild image's preinstalled Corretto 8. The image ships it at + # $JAVA_8_HOME but defaults JAVA_HOME to Java 25, so point JAVA_HOME/PATH at + # 8. Avoids actions/setup-java, which fetches from corretto.github.io + + # corretto.aws, both blocked by the runner egress lock. $JAVA_8_HOME + # resolves per-arch (x86_64/aarch64). + - name: Use the runner image's preinstalled Corretto 8 + run: | + echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" + echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" + "$JAVA_8_HOME/bin/java" -version + + # Route all mvn resolution through the CodeArtifact mirror. Must precede + # resolve-release-version, which invokes `mvn help:evaluate`. Ambient + # CodeBuild runner-role creds supply the token; no OIDC step in this job. + - name: Configure Maven CodeArtifact mirror + uses: ./.github/actions/configure-maven-mirror - name: Resolve and validate release version uses: ./.github/actions/resolve-release-version @@ -112,6 +122,12 @@ jobs: ${{ env.MODULE }}/target/*-linux*.jar ${{ env.MODULE }}/target/classes/jni/*.so + # Remove the user settings holding the CodeArtifact mirror token once the + # build is done. Ephemeral runner, so defence-in-depth, not load-bearing. + - name: Scrub Maven settings + if: always() + run: rm -f "$HOME/.m2/settings.xml" + # Assemble all native builds and publish. release: needs: build-natives @@ -123,12 +139,23 @@ jobs: with: fetch-depth: 0 # full history for tagging/pushing - - name: Set up JDK 8 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 - with: - java-version: 8 - distribution: corretto - cache: maven + # Use the CodeBuild image's preinstalled Corretto 8. The image ships it at + # $JAVA_8_HOME but defaults JAVA_HOME to Java 25, so point JAVA_HOME/PATH at + # 8. Avoids actions/setup-java, which fetches from corretto.github.io + + # corretto.aws, both blocked by the runner egress lock. $JAVA_8_HOME + # resolves per-arch (x86_64/aarch64). + - name: Use the runner image's preinstalled Corretto 8 + run: | + echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" + echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" + "$JAVA_8_HOME/bin/java" -version + + # Route all mvn resolution through the CodeArtifact mirror. Must precede + # resolve-release-version (which invokes `mvn help:evaluate`) and the OIDC + # step (which would shadow the runner-role creds this needs). Runs on every + # path, since dependency resolution happens on dry-runs too. + - name: Configure Maven CodeArtifact mirror + uses: ./.github/actions/configure-maven-mirror - name: Resolve and validate release version uses: ./.github/actions/resolve-release-version @@ -234,7 +261,11 @@ jobs: gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') - # settings.xml with the Sonatype token (server id "central"). + # Global settings holding only the Sonatype "central" server for upload. + # Passed to Maven as -gs (global) so it MERGES with the CodeArtifact + # mirror in ~/.m2/settings.xml (user) that the mirror step wrote: deps + # resolve through the mirror, upload goes to central, and the mirror + # token stays in that user file instead of being re-passed here. { echo '' echo "central" @@ -243,9 +274,9 @@ jobs: echo '' } > "$MAVEN_SETTINGS" - # --- Publish --- + # --- Publish --- (-gs: merge Sonatype creds with the ~/.m2 mirror) mvn deploy -Prelease -DskipTests -DmultiArch=false \ - -s "$MAVEN_SETTINGS" \ + -gs "$MAVEN_SETTINGS" \ -Dgpg.keyname="$GPG_KEYNAME" -Dgpg.passphrase="$GPG_PASSPHRASE" \ --file "$MODULE/pom.xml" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57ec1f3a0..b35982cbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,13 +85,22 @@ jobs: with: fetch-depth: 0 # full history for tagging/pushing - # Pinned JDK 8: building on a newer JDK can silently break the artifact. - - name: Set up JDK 8 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 - with: - java-version: 8 - distribution: corretto - cache: maven + # Use the CodeBuild image's preinstalled Corretto 8. The image ships it at + # $JAVA_8_HOME but defaults JAVA_HOME to Java 25, so point JAVA_HOME/PATH at + # 8. Avoids actions/setup-java, which fetches from corretto.github.io + + # corretto.aws, both blocked by the runner egress lock. $JAVA_8_HOME + # resolves per-arch (x86_64/aarch64). + - name: Use the runner image's preinstalled Corretto 8 + run: | + echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" + echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" + "$JAVA_8_HOME/bin/java" -version + + # Route all mvn resolution through the CodeArtifact mirror. Runs before the + # OIDC step (which would shadow the runner-role creds this needs) and on + # every path, since dependency resolution happens on dry-runs too. + - name: Configure Maven CodeArtifact mirror + uses: ./.github/actions/configure-maven-mirror - name: Resolve and validate release version uses: ./.github/actions/resolve-release-version @@ -201,7 +210,11 @@ jobs: gpg --batch --import <<< "$GPG_PRIVATE_KEY" GPG_KEYNAME=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') - # settings.xml with the Sonatype token (server id "central"). + # Global settings holding only the Sonatype "central" server for upload. + # Passed to Maven as -gs (global) so it MERGES with the CodeArtifact + # mirror in ~/.m2/settings.xml (user) that the mirror step wrote: deps + # resolve through the mirror, upload goes to central, and the mirror + # token stays in that user file instead of being re-passed here. { echo '' echo "central" @@ -220,9 +233,11 @@ jobs: # Prepare locally (no push): release commits + tag. mvn release:prepare -DpushChanges=false "${RELEASE_ARGS[@]}" --file "$MODULE/pom.xml" - # perform forks a fresh build, so pass settings/gpg via -Darguments. + # perform forks a fresh build. Pass the Sonatype creds as GLOBAL + # settings (-gs) so the fork still auto-reads ~/.m2/settings.xml (the + # mirror) and merges the two. mvn release:perform -DlocalCheckout=true \ - -Darguments="-s $MAVEN_SETTINGS -Prelease -Dgpg.keyname=$GPG_KEYNAME -Dgpg.passphrase=$GPG_PASSPHRASE" \ + -Darguments="-gs $MAVEN_SETTINGS -Prelease -Dgpg.keyname=$GPG_KEYNAME -Dgpg.passphrase=$GPG_PASSPHRASE" \ --file "$MODULE/pom.xml" # Push commits + tag atomically, only after publish succeeded. @@ -261,3 +276,11 @@ jobs: echo "| Version | \`$EFFECTIVE_RELEASE_VERSION\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`$TAG_NAME\` |" >> $GITHUB_STEP_SUMMARY echo "| Maven Central | [com.amazonaws:$MODULE:$EFFECTIVE_RELEASE_VERSION](https://central.sonatype.com/artifact/com.amazonaws/$MODULE/$EFFECTIVE_RELEASE_VERSION) |" >> $GITHUB_STEP_SUMMARY + + # Symmetry with the publish step's in-shell scrub: remove the user settings + # holding the CodeArtifact mirror token. Last step, after the mvn-using + # rollback path, so nothing still needs it. The runner is ephemeral, so + # this is defence-in-depth, not load-bearing. + - name: Scrub Maven settings + if: always() + run: rm -f "$HOME/.m2/settings.xml" From 8266bb52f251fffc37a794a9b2e402bdca879133 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 11:40:08 +0100 Subject: [PATCH 149/173] chore(release): disable Maven Central autoPublish --- aws-lambda-java-core/pom.xml | 2 +- aws-lambda-java-events-sdk-transformer/pom.xml | 2 +- aws-lambda-java-events/pom.xml | 2 +- aws-lambda-java-log4j2/pom.xml | 2 +- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- aws-lambda-java-serialization/pom.xml | 2 +- aws-lambda-java-tests/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws-lambda-java-core/pom.xml b/aws-lambda-java-core/pom.xml index e9464e3d1..f45b32fb6 100644 --- a/aws-lambda-java-core/pom.xml +++ b/aws-lambda-java-core/pom.xml @@ -154,7 +154,7 @@ true central - true + false diff --git a/aws-lambda-java-events-sdk-transformer/pom.xml b/aws-lambda-java-events-sdk-transformer/pom.xml index 1072f4cc6..f66020068 100644 --- a/aws-lambda-java-events-sdk-transformer/pom.xml +++ b/aws-lambda-java-events-sdk-transformer/pom.xml @@ -206,7 +206,7 @@ true central - true + false diff --git a/aws-lambda-java-events/pom.xml b/aws-lambda-java-events/pom.xml index 7ab9aa938..0b69b03e6 100644 --- a/aws-lambda-java-events/pom.xml +++ b/aws-lambda-java-events/pom.xml @@ -201,7 +201,7 @@ true central - true + false diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 432c4f5c4..842ef2d48 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -197,7 +197,7 @@ true central - true + false diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index a9c49c958..6db41aa36 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -416,7 +416,7 @@ true central - true + false diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 613b204c6..8b2b754af 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -178,7 +178,7 @@ true central - true + false diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index b1daf4105..bb0c7ab74 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -239,7 +239,7 @@ true central - true + false From 9bdcd398103ff8fb34b3190d23da004951542380 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 14:09:13 +0100 Subject: [PATCH 150/173] fix(ci): write JDK 8 toolchains.xml for release builds --- .../release-runtime-interface-client.yml | 22 +++++++++++++++++++ .github/workflows/release.yml | 11 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 772b12cae..f08e5ae58 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -79,6 +79,17 @@ jobs: echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" "$JAVA_8_HOME/bin/java" -version + mkdir -p "$HOME/.m2" + cat > "$HOME/.m2/toolchains.xml" < + + + jdk + 8 + $JAVA_8_HOME + + + EOF # Route all mvn resolution through the CodeArtifact mirror. Must precede # resolve-release-version, which invokes `mvn help:evaluate`. Ambient @@ -149,6 +160,17 @@ jobs: echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" "$JAVA_8_HOME/bin/java" -version + mkdir -p "$HOME/.m2" + cat > "$HOME/.m2/toolchains.xml" < + + + jdk + 8 + $JAVA_8_HOME + + + EOF # Route all mvn resolution through the CodeArtifact mirror. Must precede # resolve-release-version (which invokes `mvn help:evaluate`) and the OIDC diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b35982cbf..7ff834e56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,6 +95,17 @@ jobs: echo "JAVA_HOME=$JAVA_8_HOME" >> "$GITHUB_ENV" echo "$JAVA_8_HOME/bin" >> "$GITHUB_PATH" "$JAVA_8_HOME/bin/java" -version + mkdir -p "$HOME/.m2" + cat > "$HOME/.m2/toolchains.xml" < + + + jdk + 8 + $JAVA_8_HOME + + + EOF # Route all mvn resolution through the CodeArtifact mirror. Runs before the # OIDC step (which would shadow the runner-role creds this needs) and on From defc14b93ab939d4377c78f75535ba76bcc0906e Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Fri, 14 Aug 2026 14:51:00 +0100 Subject: [PATCH 151/173] chore: test fixing --- .github/workflows/release-runtime-interface-client.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index f08e5ae58..3792d5821 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -86,7 +86,7 @@ jobs: jdk 8 - $JAVA_8_HOME + $JAVA_HOME EOF @@ -167,7 +167,7 @@ jobs: jdk 8 - $JAVA_8_HOME + $JAVA_HOME EOF From e638ff92d1ab6faf1417d2a53fae09a584a51a75 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 15:06:45 +0100 Subject: [PATCH 152/173] commenting out main branch requirement for testing --- .../release-runtime-interface-client.yml | 18 +++++++++--------- .github/workflows/release.yml | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 3792d5821..ed8865b99 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -57,15 +57,15 @@ jobs: runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: - # Manual (workflow_dispatch) releases must only run from main, never from - # an arbitrary branch that could carry unreviewed release logic. Guarding - # the first job blocks the whole pipeline (release needs build-natives). - - name: Verify release branch - run: | - if [[ "$GITHUB_REF_NAME" != "main" ]]; then - echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" - exit 1 - fi + # # Manual (workflow_dispatch) releases must only run from main, never from + # # an arbitrary branch that could carry unreviewed release logic. Guarding + # # the first job blocks the whole pipeline (release needs build-natives). + # - name: Verify release branch + # run: | + # if [[ "$GITHUB_REF_NAME" != "main" ]]; then + # echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + # exit 1 + # fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ff834e56..dc446f7f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,12 +74,12 @@ jobs: steps: # Manual (workflow_dispatch) releases must only run from main, never from # an arbitrary branch that could carry unreviewed release logic. - - name: Verify release branch - run: | - if [[ "$GITHUB_REF_NAME" != "main" ]]; then - echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" - exit 1 - fi + # - name: Verify release branch + # run: | + # if [[ "$GITHUB_REF_NAME" != "main" ]]; then + # echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + # exit 1 + # fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: From a035af9c6cb3935015be4e1ca30c2fd767e175ef Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 16:19:28 +0100 Subject: [PATCH 153/173] ci(ric): pull JNI base images via ECR pull-through cache --- .../release-runtime-interface-client.yml | 21 +++++++++++++++++++ .../src/main/jni/Dockerfile.glibc | 3 ++- .../src/main/jni/Dockerfile.musl | 3 ++- .../src/main/jni/build-jni-lib.sh | 8 +++++-- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index ed8865b99..b5122f31b 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -40,6 +40,11 @@ env: MAVEN_ARGS: "-B --no-transfer-progress" AWS_REGION: ${{ vars.AWS_REGION_MAVEN_RELEASE }} OIDC_ROLE_ARN: ${{ secrets.AWS_ROLE_MAVEN_RELEASE }} + # ECR pull-through cache used for the native JNI base images. ECR_REGISTRY is + # the login target; BASE_REGISTRY (with the /ecr-public prefix) is passed to + # the Dockerfiles as a build-arg. + ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION_MAVEN_RELEASE }}.amazonaws.com + BASE_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION_MAVEN_RELEASE }}.amazonaws.com/ecr-public jobs: # Build each architecture's native libs (glibc + musl) on a native runner. @@ -103,6 +108,14 @@ jobs: module: ${{ env.MODULE }} release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + # The native JNI build shells out to `docker build` against the ECR + # pull-through cache (see src/main/jni/Dockerfile.*). Authenticate first so + # the base-image pulls don't hit public.ecr.aws. Uses ambient runner creds. + - name: Log in to Amazon ECR (pull-through cache) + run: | + aws ecr get-login-password --region "$AWS_REGION" \ + | docker login --username AWS --password-stdin "$ECR_REGISTRY" + # -DskipTests: only installed so the module compiles, not released here. - name: Install intra-repo dependencies run: | @@ -185,6 +198,14 @@ jobs: module: ${{ env.MODULE }} release-version-override: ${{ env.RELEASE_VERSION_INPUT }} + # The native JNI build shells out to `docker build` against the ECR + # pull-through cache (see src/main/jni/Dockerfile.*). Authenticate first so + # the base-image pulls don't hit public.ecr.aws. Uses ambient runner creds. + - name: Log in to Amazon ECR (pull-through cache) + run: | + aws ecr get-login-password --region "$AWS_REGION" \ + | docker login --username AWS --password-stdin "$ECR_REGISTRY" + - name: Resolve next development version and tag run: | # Next development version: use the override, or bump the patch. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index 1cfcfbb1d..a8adb18e6 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -1,4 +1,5 @@ -FROM public.ecr.aws/amazonlinux/amazonlinux:2 +ARG BASE_REGISTRY=public.ecr.aws +FROM ${BASE_REGISTRY}/amazonlinux/amazonlinux:2 ARG CURL_VERSION diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl index 64725c140..5fd7f4882 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl @@ -1,4 +1,5 @@ -FROM public.ecr.aws/docker/library/alpine:3 +ARG BASE_REGISTRY=public.ecr.aws +FROM ${BASE_REGISTRY}/docker/library/alpine:3 ARG CURL_VERSION diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index b7dbb5a80..69b9a5125 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -9,6 +9,10 @@ MULTI_ARCH=${2} BUILD_OS=${3} BUILD_ARCH=${4} CURL_VERSION=7.83.1 +# Registry hosting the base images. Defaults to public.ecr.aws for local and +# GitHub-hosted builds; the release workflow overrides it with the ECR +# pull-through cache so egress-locked runners don't hit public.ecr.aws. +BASE_REGISTRY="${BASE_REGISTRY:-public.ecr.aws}" function get_docker_platform() { arch=$1 @@ -45,7 +49,7 @@ function build_for_libc_arch() { if [[ "${MULTI_ARCH}" == "true" ]]; then docker build --platform="${docker_platform}" -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" -o - \ + --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} "${SRC_DIR}" -o - \ | tar -xOf - src/aws-lambda-runtime-interface-client.so > "${artifact}" else echo "multi-arch not requested, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." @@ -63,7 +67,7 @@ function build_for_libc_arch() { docker build --platform="${docker_platform}" \ -t "${image_name}" \ -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} "${SRC_DIR}" ${EXTRA_LOAD_ARG} + --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} "${SRC_DIR}" ${EXTRA_LOAD_ARG} echo "Docker image has been successfully built" From 00e7ed523c380a4187b4ff03d23345217b7f9b71 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 15:34:13 +0000 Subject: [PATCH 154/173] fix: set AWS_REGION --- .../src/main/jni/Dockerfile.glibc | 3 +++ .../src/main/jni/build-jni-lib.sh | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index a8adb18e6..7ad20122c 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -2,6 +2,9 @@ ARG BASE_REGISTRY=public.ecr.aws FROM ${BASE_REGISTRY}/amazonlinux/amazonlinux:2 ARG CURL_VERSION +ARG AWS_REGION + +RUN if [ -n "${AWS_REGION}" ]; then echo "${AWS_REGION}" > /etc/yum/vars/awsregion; fi RUN yum install -y \ cmake3 \ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 69b9a5125..44a290b6a 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -13,6 +13,7 @@ CURL_VERSION=7.83.1 # GitHub-hosted builds; the release workflow overrides it with the ECR # pull-through cache so egress-locked runners don't hit public.ecr.aws. BASE_REGISTRY="${BASE_REGISTRY:-public.ecr.aws}" +AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-}}" function get_docker_platform() { arch=$1 @@ -49,7 +50,7 @@ function build_for_libc_arch() { if [[ "${MULTI_ARCH}" == "true" ]]; then docker build --platform="${docker_platform}" -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} "${SRC_DIR}" -o - \ + --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} --build-arg AWS_REGION=${AWS_REGION} "${SRC_DIR}" -o - \ | tar -xOf - src/aws-lambda-runtime-interface-client.so > "${artifact}" else echo "multi-arch not requested, assuming this is a workaround to goofyness when docker buildx is enabled on Linux CI environments." @@ -67,7 +68,7 @@ function build_for_libc_arch() { docker build --platform="${docker_platform}" \ -t "${image_name}" \ -f "${SRC_DIR}/Dockerfile.${libc_impl}" \ - --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} "${SRC_DIR}" ${EXTRA_LOAD_ARG} + --build-arg CURL_VERSION=${CURL_VERSION} --build-arg BASE_REGISTRY=${BASE_REGISTRY} --build-arg AWS_REGION=${AWS_REGION} "${SRC_DIR}" ${EXTRA_LOAD_ARG} echo "Docker image has been successfully built" From c5c8805eb847870f98325c6bc8469eb04d2f180f Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 16:57:20 +0100 Subject: [PATCH 155/173] ci(ric): temporarily disable musl builds for testing --- .../workflows/release-runtime-interface-client.yml | 4 ++-- aws-lambda-java-runtime-interface-client/pom.xml | 13 +++---------- .../src/main/jni/build-jni-lib.sh | 4 +++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index b5122f31b..c0ba2d84d 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -55,10 +55,10 @@ jobs: include: - arch: x86_64 runner: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} - profiles: linux-x86_64 linux_musl-x86_64 + profiles: linux-x86_64 - arch: aarch64 runner: codebuild-aws-lambda-java-libs-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} - profiles: linux-aarch64 linux_musl-aarch64 + profiles: linux-aarch64 runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 6db41aa36..c76f37610 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -442,16 +442,9 @@ jar linux-aarch_64 - - ${project.build.directory}/${project.build.finalName}-linux_musl-x86_64.jar - jar - linux_musl-x86_64 - - - ${project.build.directory}/${project.build.finalName}-linux_musl-aarch_64.jar - jar - linux_musl-aarch_64 - + diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 44a290b6a..82e49d566 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -116,7 +116,9 @@ if [ -n "$BUILD_OS" ] && [ -n "$BUILD_ARCH" ]; then else # build for all architectures and libc implementations declare -a ARCHITECTURES=("x86_64" "aarch_64") - declare -a LIBC_IMPLS=("glibc" "musl") + # musl (Alpine) temporarily disabled for testing: the egress-locked release + # runner can't reach the Alpine apk mirrors. Re-add "musl" once that's solved. + declare -a LIBC_IMPLS=("glibc") for arch in "${ARCHITECTURES[@]}"; do From 25a85f5a554b6a648ace6f6cfd711c86988c0bd9 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 16:42:00 +0000 Subject: [PATCH 156/173] fix: fix egress test --- .../com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java index 5eb6f749f..721b27059 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/crac/DNSCacheManagerTest.java @@ -69,7 +69,7 @@ public void positiveDnsCacheShouldBeEmpty() throws CheckpointException, RestoreE StatefulResource resource = new StatefulResource(); Core.getGlobalContext().register(resource); - String[] hosts = {"www.stackoverflow.com", "www.amazon.com", "www.yahoo.com"}; + String[] hosts = {"github.com", "amazonaws.com"}; for(String singleHost : hosts) { InetAddress address = InetAddress.getByName(singleHost); } From c6defea2abcfe8de07a49c63dc315718a8361471 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 16:56:21 +0000 Subject: [PATCH 157/173] fix: remove breaking test (test only) --- .../runtime/api/client/ClasspathLoaderTest.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java index 38147d219..49ffa59d2 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java @@ -35,20 +35,6 @@ void testLoadAllClassesWithNoClasspath() throws IOException { } } - @Test - void testLoadAllClassesWithEmptyClasspath() { - String originalClasspath = System.getProperty("java.class.path"); - try { - System.setProperty("java.class.path", ""); - assertThrows(FileNotFoundException.class, () -> - ClasspathLoader.main(new String[]{})); - } finally { - if (originalClasspath != null) { - System.setProperty("java.class.path", originalClasspath); - } - } - } - @Test void testLoadAllClassesWithInvalidPath() { String originalClasspath = System.getProperty("java.class.path"); From c583fae2ec7f6aead477055c9bf84f68cac95fd2 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 16:57:17 +0000 Subject: [PATCH 158/173] Revert "fix: remove breaking test (test only)" This reverts commit c6defea2abcfe8de07a49c63dc315718a8361471. --- .../runtime/api/client/ClasspathLoaderTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java index 49ffa59d2..38147d219 100644 --- a/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java +++ b/aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/ClasspathLoaderTest.java @@ -35,6 +35,20 @@ void testLoadAllClassesWithNoClasspath() throws IOException { } } + @Test + void testLoadAllClassesWithEmptyClasspath() { + String originalClasspath = System.getProperty("java.class.path"); + try { + System.setProperty("java.class.path", ""); + assertThrows(FileNotFoundException.class, () -> + ClasspathLoader.main(new String[]{})); + } finally { + if (originalClasspath != null) { + System.setProperty("java.class.path", originalClasspath); + } + } + } + @Test void testLoadAllClassesWithInvalidPath() { String originalClasspath = System.getProperty("java.class.path"); From a0a326b8503bfac928d23e6d743d5b4b8016c93b Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 17:02:46 +0000 Subject: [PATCH 159/173] fix: java8 home --- .github/workflows/release-runtime-interface-client.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index c0ba2d84d..eb48b7525 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -91,7 +91,7 @@ jobs: jdk 8 - $JAVA_HOME + $JAVA_8_HOME EOF @@ -180,7 +180,7 @@ jobs: jdk 8 - $JAVA_HOME + $JAVA_8_HOME EOF From d45cfe874e3e0616fa5a00054b253e85d149fec0 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 18:20:16 +0100 Subject: [PATCH 160/173] ci(ric): blank Java 9+ argLine for JDK 8 test run --- .github/workflows/release-runtime-interface-client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index eb48b7525..c003c79ae 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -243,7 +243,7 @@ jobs: - name: Run tests env: IS_JAVA_8: true - run: mvn test --file "$MODULE/pom.xml" + run: mvn test -DargLineForReflectionTestOnly="" --file "$MODULE/pom.xml" # JARs to attach + .so files for the fat main JAR. - name: Download native artifacts From 306234c05bde2d3ecc9d1462ed7aaf64068792bd Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 18:39:21 +0100 Subject: [PATCH 161/173] fix(ci): raise OIDC role duration to STS minimum 900s --- .github/actions/configure-release-aws-credentials/action.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/actions/configure-release-aws-credentials/action.yml b/.github/actions/configure-release-aws-credentials/action.yml index a02a1e9a4..6a34f6480 100644 --- a/.github/actions/configure-release-aws-credentials/action.yml +++ b/.github/actions/configure-release-aws-credentials/action.yml @@ -23,5 +23,6 @@ runs: aws-region: ${{ inputs.aws-region }} role-to-assume: ${{ inputs.role-to-assume }} role-session-name: ${{ inputs.role-session-name }} - # Short-lived: the job only needs the role briefly to read two secrets. - role-duration-seconds: 300 + # Kept short: the job only needs the role briefly to read two secrets. + # 900s is STS's minimum for assume-role; anything lower is rejected. + role-duration-seconds: 900 From 3a17a26251d770984fbf280a16f7051ab57006b5 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 14 Aug 2026 18:25:24 +0000 Subject: [PATCH 162/173] Revert "ci(ric): temporarily disable musl builds for testing" This reverts commit c5c8805eb847870f98325c6bc8469eb04d2f180f. --- .../workflows/release-runtime-interface-client.yml | 4 ++-- aws-lambda-java-runtime-interface-client/pom.xml | 13 ++++++++++--- .../src/main/jni/build-jni-lib.sh | 4 +--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index c003c79ae..49c3f8d5a 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -55,10 +55,10 @@ jobs: include: - arch: x86_64 runner: codebuild-aws-lambda-java-libs-test-trigger-x86-${{ github.run_id }}-${{ github.run_attempt }} - profiles: linux-x86_64 + profiles: linux-x86_64 linux_musl-x86_64 - arch: aarch64 runner: codebuild-aws-lambda-java-libs-test-trigger-arm64-${{ github.run_id }}-${{ github.run_attempt }} - profiles: linux-aarch64 + profiles: linux-aarch64 linux_musl-aarch64 runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index c76f37610..6db41aa36 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -442,9 +442,16 @@ jar linux-aarch_64 - + + ${project.build.directory}/${project.build.finalName}-linux_musl-x86_64.jar + jar + linux_musl-x86_64 + + + ${project.build.directory}/${project.build.finalName}-linux_musl-aarch_64.jar + jar + linux_musl-aarch_64 + diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 82e49d566..44a290b6a 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -116,9 +116,7 @@ if [ -n "$BUILD_OS" ] && [ -n "$BUILD_ARCH" ]; then else # build for all architectures and libc implementations declare -a ARCHITECTURES=("x86_64" "aarch_64") - # musl (Alpine) temporarily disabled for testing: the egress-locked release - # runner can't reach the Alpine apk mirrors. Re-add "musl" once that's solved. - declare -a LIBC_IMPLS=("glibc") + declare -a LIBC_IMPLS=("glibc" "musl") for arch in "${ARCHITECTURES[@]}"; do From 69892c1005569a8fab4abf2d6bc82c1782617c34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Aug 2026 18:56:20 +0000 Subject: [PATCH 163/173] chore(ric): release 2.12.0-RC --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index 6db41aa36..da9209bbb 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.12.0-SNAPSHOT + 2.12.0-RC jar AWS Lambda Java Runtime Interface Client From cbb4dd0ed547080d8ed77d7dcd30ed538871360d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Aug 2026 18:56:22 +0000 Subject: [PATCH 164/173] chore(ric): prepare next development 2.12.1-SNAPSHOT --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index da9209bbb..d84aa51d8 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.12.0-RC + 2.12.1-SNAPSHOT jar AWS Lambda Java Runtime Interface Client From e4713720b62904687eef8a420867f5939caa5236 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 20:02:02 +0100 Subject: [PATCH 165/173] ci: restore main-only release branch guard --- .../release-runtime-interface-client.yml | 18 +++++++++--------- .github/workflows/release.yml | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-runtime-interface-client.yml b/.github/workflows/release-runtime-interface-client.yml index 49c3f8d5a..e41dcfc61 100644 --- a/.github/workflows/release-runtime-interface-client.yml +++ b/.github/workflows/release-runtime-interface-client.yml @@ -62,15 +62,15 @@ jobs: runs-on: ${{ matrix.runner }} timeout-minutes: 45 steps: - # # Manual (workflow_dispatch) releases must only run from main, never from - # # an arbitrary branch that could carry unreviewed release logic. Guarding - # # the first job blocks the whole pipeline (release needs build-natives). - # - name: Verify release branch - # run: | - # if [[ "$GITHUB_REF_NAME" != "main" ]]; then - # echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" - # exit 1 - # fi + # Manual (workflow_dispatch) releases must only run from main, never from + # an arbitrary branch that could carry unreviewed release logic. Guarding + # the first job blocks the whole pipeline (release needs build-natives). + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc446f7f5..7ff834e56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,12 +74,12 @@ jobs: steps: # Manual (workflow_dispatch) releases must only run from main, never from # an arbitrary branch that could carry unreviewed release logic. - # - name: Verify release branch - # run: | - # if [[ "$GITHUB_REF_NAME" != "main" ]]; then - # echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" - # exit 1 - # fi + - name: Verify release branch + run: | + if [[ "$GITHUB_REF_NAME" != "main" ]]; then + echo "::error::Releases must run from the main branch, got '$GITHUB_REF_NAME'" + exit 1 + fi - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: From e6290f4ea8b8c9216f4b9d04a68b811ef506c729 Mon Sep 17 00:00:00 2001 From: Fabiana Severin Date: Fri, 14 Aug 2026 20:07:58 +0100 Subject: [PATCH 166/173] chore(ric): revert version bump to 2.12.0-SNAPSHOT --- aws-lambda-java-runtime-interface-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-runtime-interface-client/pom.xml b/aws-lambda-java-runtime-interface-client/pom.xml index d84aa51d8..6db41aa36 100644 --- a/aws-lambda-java-runtime-interface-client/pom.xml +++ b/aws-lambda-java-runtime-interface-client/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.amazonaws aws-lambda-java-runtime-interface-client - 2.12.1-SNAPSHOT + 2.12.0-SNAPSHOT jar AWS Lambda Java Runtime Interface Client From b6f57da1a1dda5547abe5d2a46f459e1262d39bd Mon Sep 17 00:00:00 2001 From: Rudraroop Ray Date: Wed, 19 Aug 2026 16:42:02 +0000 Subject: [PATCH 167/173] Jackson version bump to 2.18.9 for CVE --- aws-lambda-java-serialization/RELEASE.CHANGELOG.md | 4 ++++ aws-lambda-java-serialization/pom.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md index 3bb977937..2aaa8732a 100644 --- a/aws-lambda-java-serialization/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-serialization/RELEASE.CHANGELOG.md @@ -1,3 +1,7 @@ +### Aug 20, 2026 +`1.4.2`: +- Update `jackson-databind` dependency from 2.18.6 to 2.18.9 + ### May 20, 2026 `1.4.1`: - Fix build issue diff --git a/aws-lambda-java-serialization/pom.xml b/aws-lambda-java-serialization/pom.xml index 8b2b754af..fbdb93d9a 100644 --- a/aws-lambda-java-serialization/pom.xml +++ b/aws-lambda-java-serialization/pom.xml @@ -35,7 +35,7 @@ 1.8 1.8 com.amazonaws.lambda.thirdparty - 2.18.6 + 2.18.9 2.10.1 20231013 7.3.2 From c38b1b13e347ad2558c48912edf816f95c221aa2 Mon Sep 17 00:00:00 2001 From: Rudraroop Ray Date: Wed, 19 Aug 2026 17:02:14 +0000 Subject: [PATCH 168/173] Adding next version of serialization post auto-bump to the test package --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index bb0c7ab74..40d4d0c37 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -43,7 +43,7 @@ --> 5.9.2 0.8.7 - 1.4.1 + 1.4.2 3.16.1 3.18.0 3.27.7 From 9bd304e308b2ed54899fde771730def99d022d30 Mon Sep 17 00:00:00 2001 From: Rudraroop Ray Date: Thu, 20 Aug 2026 10:08:00 +0000 Subject: [PATCH 169/173] Revert "Adding next version of serialization post auto-bump to the test package" This reverts commit c38b1b13e347ad2558c48912edf816f95c221aa2. --- aws-lambda-java-tests/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-lambda-java-tests/pom.xml b/aws-lambda-java-tests/pom.xml index 40d4d0c37..bb0c7ab74 100644 --- a/aws-lambda-java-tests/pom.xml +++ b/aws-lambda-java-tests/pom.xml @@ -43,7 +43,7 @@ --> 5.9.2 0.8.7 - 1.4.2 + 1.4.1 3.16.1 3.18.0 3.27.7 From a32b1e4ee21bf8e6284010eaca92eefb9f81a7dc Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 20 Aug 2026 08:41:47 -0400 Subject: [PATCH 170/173] fix: speed up CI (#634) * fix: speed up CI * fix: build native JNI lib on aarch64 CI runners The multiArch=false path skips architectures whose name doesn't match the host arch, but ARCHITECTURES uses Maven's classifier spelling 'aarch_64' while `arch` reports 'aarch64'. On the ARM runner this mismatch caused every arch to be skipped, so no .so was built and the unit tests crashed loading the native library. Normalize the host arch to 'aarch_64' before comparing. * fix: speed up smoke tests * fix: pr comments --- .../workflows/runtime-interface-client_pr.yml | 66 ++++++++++++++----- .../Makefile | 31 +++++++-- .../src/main/jni/build-jni-lib.sh | 11 +++- .../codebuild/buildspec.os.alpine.yml | 2 +- .../codebuild/buildspec.os.amazoncorretto.yml | 2 +- 5 files changed, 89 insertions(+), 23 deletions(-) diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index a0d8c6cc8..b8fb8cfbe 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -19,8 +19,17 @@ permissions: jobs: - smoke-test: - runs-on: ubuntu-latest + smoke-test-arch: + strategy: + fail-fast: true + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + name: "smoke-test (${{ matrix.arch }})" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -39,14 +48,23 @@ jobs: working-directory: ./aws-lambda-java-serialization run: mvn clean install - - name: Runtime Interface Client smoke tests - Run 'pr' target + - name: Runtime Interface Client smoke tests - Run 'pr-${{ matrix.arch }}' target working-directory: ./aws-lambda-java-runtime-interface-client - run: make pr + run: make pr-${{ matrix.arch }} env: IS_JAVA_8: true - build: - runs-on: ubuntu-latest + build-arch: + strategy: + fail-fast: true + matrix: + include: + - arch: x86_64 + runner: ubuntu-latest + - arch: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + name: "build (${{ matrix.arch }})" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -57,17 +75,11 @@ jobs: distribution: corretto cache: maven - - name: Set up QEMU - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: install: true - - name: Available buildx platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - name: Build and install core dependency locally working-directory: ./aws-lambda-java-core run: mvn clean install @@ -76,16 +88,16 @@ jobs: working-directory: ./aws-lambda-java-serialization run: mvn clean install - - name: Test Runtime Interface Client xplatform build - Run 'build' target + - name: Test Runtime Interface Client build - Run 'build-${{ matrix.arch }}' target working-directory: ./aws-lambda-java-runtime-interface-client - run: make build + run: make build-${{ matrix.arch }} env: IS_JAVA_8: true - name: Save the built jar uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: aws-lambda-java-runtime-interface-client + name: aws-lambda-java-runtime-interface-client-${{ matrix.arch }} path: ./aws-lambda-java-runtime-interface-client/target/aws-lambda-java-runtime-interface-client-*.jar - name: Upload coverage to Codecov @@ -93,3 +105,27 @@ jobs: uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + smoke-test: + needs: smoke-test-arch + if: always() + runs-on: ubuntu-latest + steps: + - name: Check smoke-test results + run: | + if [ "${{ needs.smoke-test-arch.result }}" != "success" ]; then + echo "Smoke tests failed on one or more architectures" + exit 1 + fi + + build: + needs: build-arch + if: always() + runs-on: ubuntu-latest + steps: + - name: Check build results + run: | + if [ "${{ needs.build-arch.result }}" != "success" ]; then + echo "Build failed on one or more architectures" + exit 1 + fi diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index 6c3a268fb..e651abc96 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -30,11 +30,20 @@ setup-codebuild-agent: --build-arg ARCHITECTURE=$(ARCHITECTURE_ALIAS) \ - < test/integration/codebuild-local/Dockerfile.agent +# Smoke tests are split per-architecture so CI can run each set on a native +# runner. Running the linux/arm64/v8 combos under QEMU on an x86_64 host makes +# `mvn install` recompile curl for aarch64 emulated, which takes ~30 minutes. .PHONY: test-smoke -test-smoke: setup-codebuild-agent +test-smoke: test-smoke-x86_64 test-smoke-aarch64 + +.PHONY: test-smoke-x86_64 +test-smoke-x86_64: setup-codebuild-agent CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 corretto11 linux/amd64 - CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 corretto11 linux/arm64/v8 CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.amazoncorretto.yml amazoncorretto amazoncorretto 11 linux/amd64 + +.PHONY: test-smoke-aarch64 +test-smoke-aarch64: setup-codebuild-agent + CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 corretto11 linux/arm64/v8 CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.amazoncorretto.yml amazoncorretto amazoncorretto 11 linux/arm64/v8 .PHONY: test-integ @@ -49,11 +58,25 @@ dev: test .PHONY: pr pr: test test-smoke +# Per-architecture PR checks so CI can run each on a native runner (no QEMU). +.PHONY: pr-x86_64 +pr-x86_64: test test-smoke-x86_64 + +.PHONY: pr-aarch64 +pr-aarch64: test test-smoke-aarch64 + .PHONY: build -build: - mvn clean install $(EXTRA_LOAD_ARG) +build: build-x86_64 build-aarch64 + +.PHONY: build-x86_64 +build-x86_64: + mvn clean install -DmultiArch=false $(EXTRA_LOAD_ARG) mvn install -P linux-x86_64 $(EXTRA_LOAD_ARG) mvn install -P linux_musl-x86_64 $(EXTRA_LOAD_ARG) + +.PHONY: build-aarch64 +build-aarch64: + mvn clean install -DmultiArch=false $(EXTRA_LOAD_ARG) mvn install -P linux-aarch64 $(EXTRA_LOAD_ARG) mvn install -P linux_musl-aarch64 $(EXTRA_LOAD_ARG) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 44a290b6a..5323030fa 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -118,10 +118,17 @@ else declare -a ARCHITECTURES=("x86_64" "aarch_64") declare -a LIBC_IMPLS=("glibc" "musl") + # `arch` reports the host as `aarch64`, but we use Maven's classifier + # spelling `aarch_64` in ARCHITECTURES, so normalize before comparing. + host_arch=$(arch) + if [ "${host_arch}" == "aarch64" ]; then + host_arch="aarch_64" + fi + for arch in "${ARCHITECTURES[@]}"; do - if [[ "${MULTI_ARCH}" != "true" ]] && [[ "$(arch)" != "${arch}" ]]; then - echo "multi arch build not requested and host arch is $(arch), so skipping ${arch}..." + if [[ "${MULTI_ARCH}" != "true" ]] && [[ "${host_arch}" != "${arch}" ]]; then + echo "multi arch build not requested and host arch is ${host_arch}, so skipping ${arch}..." continue fi diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index 2a71cb1b0..afc68fd76 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -45,7 +45,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index db8bf2ba0..e5a586b91 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -44,7 +44,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" From f4363f432dc62f5abc4a4dfd13d9ad36be1d5dde Mon Sep 17 00:00:00 2001 From: Maxime David Date: Thu, 20 Aug 2026 11:26:08 -0400 Subject: [PATCH 171/173] feat: remove vendored aws-lambda-cpp dependency (#633) --- .../workflows/runtime-interface-client_pr.yml | 55 + .../.gitignore | 3 + .../Makefile | 11 +- .../README.md | 2 +- .../src/main/jni/Dockerfile.glibc | 19 +- .../src/main/jni/Dockerfile.musl | 14 +- .../src/main/jni/build-jni-lib.sh | 73 +- .../deps/aws-lambda-cpp-0.2.7/.clang-format | 61 - .../jni/deps/aws-lambda-cpp-0.2.7/.clang-tidy | 41 - .../.github/PULL_REQUEST_TEMPLATE.md | 6 - .../jni/deps/aws-lambda-cpp-0.2.7/.gitignore | 5 - .../deps/aws-lambda-cpp-0.2.7/CMakeLists.txt | 132 - .../aws-lambda-cpp-0.2.7/CODE_OF_CONDUCT.md | 4 - .../deps/aws-lambda-cpp-0.2.7/CONTRIBUTING.md | 61 - .../jni/deps/aws-lambda-cpp-0.2.7/LICENSE | 202 - .../main/jni/deps/aws-lambda-cpp-0.2.7/NOTICE | 2 - .../jni/deps/aws-lambda-cpp-0.2.7/README.md | 220 - .../ci/codebuild/amazonlinux-2017.03.yml | 18 - .../ci/codebuild/build-cpp-sdk.sh | 17 - .../ci/codebuild/build.sh | 11 - .../ci/codebuild/format-check.sh | 24 - .../ci/codebuild/run-tests.sh | 11 - .../ci/codebuild/ubuntu-18.04.yml | 17 - .../ci/docker/alpine-linux-3.8 | 5 - .../ci/docker/amazon-linux-2017.03 | 12 - .../ci/docker/ubuntu-linux-18.04 | 15 - .../cmake/aws-lambda-runtime-config.cmake | 20 - .../aws-lambda-cpp-0.2.7/examples/Dockerfile | 3 - .../examples/api-gateway/CMakeLists.txt | 12 - .../examples/api-gateway/README.md | 81 - .../examples/api-gateway/main.cpp | 61 - .../examples/dynamodb/CMakeLists.txt | 24 - .../examples/dynamodb/README.md | 52 - .../examples/dynamodb/main.cpp | 229 - .../examples/s3/CMakeLists.txt | 22 - .../examples/s3/README.md | 51 - .../aws-lambda-cpp-0.2.7/examples/s3/main.cpp | 128 - .../include/aws/http/response.h | 174 - .../include/aws/lambda-runtime/outcome.h | 96 - .../include/aws/lambda-runtime/runtime.h | 197 - .../include/aws/lambda-runtime/version.h | 41 - .../include/aws/logging/logging.h | 67 - .../aws-lambda-cpp-0.2.7/packaging/packager | 180 - .../aws-lambda-cpp-0.2.7/src/backward.cpp | 32 - .../deps/aws-lambda-cpp-0.2.7/src/backward.h | 4291 ----- .../deps/aws-lambda-cpp-0.2.7/src/logging.cpp | 70 - .../deps/aws-lambda-cpp-0.2.7/src/runtime.cpp | 545 - .../aws-lambda-cpp-0.2.7/src/version.cpp.in | 48 - .../aws-lambda-cpp-0.2.7/tests/CMakeLists.txt | 16 - .../tests/gtest/.clang-tidy | 3 - .../tests/gtest/gtest-all.cc | 11763 ------------ .../aws-lambda-cpp-0.2.7/tests/gtest/gtest.h | 14916 ---------------- .../deps/aws-lambda-cpp-0.2.7/tests/main.cpp | 38 - .../tests/resources/CMakeLists.txt | 14 - .../tests/resources/lambda_function.cpp | 129 - .../tests/runtime_tests.cpp | 194 - .../tests/version_tests.cpp | 22 - .../codebuild-local/Dockerfile.agent | 6 +- .../codebuild-local/docker-retry.sh | 35 + .../integration/codebuild-local/test_all.sh | 23 +- .../integration/codebuild-local/test_one.sh | 6 +- .../codebuild/buildspec.os.alpine.yml | 2 +- .../codebuild/buildspec.os.amazoncorretto.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.1.yml | 2 +- .../codebuild/buildspec.os.amazonlinux.2.yml | 2 +- .../codebuild/buildspec.os.centos.yml | 81 - .../codebuild/buildspec.os.debian.yml | 8 +- .../codebuild/buildspec.os.ubuntu.yml | 8 +- .../scripts/configure_multi_arch_env.sh | 2 +- .../docker/Dockerfile.function.centos | 13 - .../docker/Dockerfile.function.debian | 6 +- 71 files changed, 217 insertions(+), 34539 deletions(-) delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-format delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-tidy delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.github/PULL_REQUEST_TEMPLATE.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.gitignore delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CODE_OF_CONDUCT.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CONTRIBUTING.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/LICENSE delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/NOTICE delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/README.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/amazonlinux-2017.03.yml delete mode 100755 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build-cpp-sdk.sh delete mode 100755 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build.sh delete mode 100755 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/format-check.sh delete mode 100755 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/run-tests.sh delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/ubuntu-18.04.yml delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/docker/alpine-linux-3.8 delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/docker/amazon-linux-2017.03 delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/docker/ubuntu-linux-18.04 delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/cmake/aws-lambda-runtime-config.cmake delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/Dockerfile delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/README.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/main.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/README.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/main.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/README.md delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/main.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/http/response.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/outcome.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/version.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/logging/logging.h delete mode 100755 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/packaging/packager delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/logging.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/version.cpp.in delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/.clang-tidy delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest-all.cc delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest.h delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/main.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/CMakeLists.txt delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/lambda_function.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/runtime_tests.cpp delete mode 100644 aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/version_tests.cpp create mode 100755 aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh delete mode 100644 aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml delete mode 100644 aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.centos diff --git a/.github/workflows/runtime-interface-client_pr.yml b/.github/workflows/runtime-interface-client_pr.yml index b8fb8cfbe..bc9e3f3eb 100644 --- a/.github/workflows/runtime-interface-client_pr.yml +++ b/.github/workflows/runtime-interface-client_pr.yml @@ -106,6 +106,61 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + integration-test-matrix: + runs-on: ${{ matrix.arch.runner }} + strategy: + # Run every OS/arch combination to completion so one failure doesn't mask the others. + fail-fast: false + matrix: + buildspec: + - buildspec.os.alpine.yml + - buildspec.os.amazoncorretto.yml + - buildspec.os.amazonlinux.1.yml + - buildspec.os.amazonlinux.2.yml + - buildspec.os.debian.yml + - buildspec.os.ubuntu.yml + arch: + - label: x64 + runner: ubuntu-latest + platform: linux/amd64 + - label: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64/v8 + exclude: + # Amazon Linux 1 was never published for ARM64 (x86_64 only), so + # public.ecr.aws/amazonlinux/amazonlinux:1 has no arm64 manifest. + - buildspec: buildspec.os.amazonlinux.1.yml + arch: + label: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64/v8 + name: "integration-test (${{ matrix.buildspec }} / ${{ matrix.arch.label }})" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + with: + install: true + + - name: Run OS integration test - 'test-integ' target + working-directory: ./aws-lambda-java-runtime-interface-client + run: make test-integ BUILDSPEC=test/integration/codebuild/${{ matrix.buildspec }} + env: + PLATFORM_FILTER: ${{ matrix.arch.platform }} + + integration-test: + needs: integration-test-matrix + if: always() + runs-on: ubuntu-latest + steps: + - name: Check integration-test results + run: | + if [ "${{ needs.integration-test-matrix.result }}" != "success" ]; then + echo "Integration tests failed on one or more OS/arch combinations" + exit 1 + fi + smoke-test: needs: smoke-test-arch if: always() diff --git a/aws-lambda-java-runtime-interface-client/.gitignore b/aws-lambda-java-runtime-interface-client/.gitignore index b1e77deb4..f6064106d 100644 --- a/aws-lambda-java-runtime-interface-client/.gitignore +++ b/aws-lambda-java-runtime-interface-client/.gitignore @@ -1,2 +1,5 @@ compile-flags.txt ric-dev-environment/codeartifact-properties.mk + +# aws-lambda-cpp prebuilt lib + headers, fetched and staged at build time +src/main/jni/deps/aws-lambda-cpp/ diff --git a/aws-lambda-java-runtime-interface-client/Makefile b/aws-lambda-java-runtime-interface-client/Makefile index e651abc96..770671c07 100644 --- a/aws-lambda-java-runtime-interface-client/Makefile +++ b/aws-lambda-java-runtime-interface-client/Makefile @@ -26,9 +26,10 @@ test: .PHONY: setup-codebuild-agent setup-codebuild-agent: - docker build -t codebuild-agent \ + test/integration/codebuild-local/docker-retry.sh docker build --load -t codebuild-agent \ --build-arg ARCHITECTURE=$(ARCHITECTURE_ALIAS) \ - - < test/integration/codebuild-local/Dockerfile.agent + -f test/integration/codebuild-local/Dockerfile.agent \ + test/integration/codebuild-local # Smoke tests are split per-architecture so CI can run each set on a native # runner. Running the linux/arm64/v8 combos under QEMU on an x86_64 host makes @@ -46,9 +47,13 @@ test-smoke-aarch64: setup-codebuild-agent CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 corretto11 linux/arm64/v8 CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.amazoncorretto.yml amazoncorretto amazoncorretto 11 linux/arm64/v8 +# BUILDSPEC can point to the buildspec directory (default, runs every OS) or to a +# single buildspec file, which is how CI parallelizes the run across OSes. +BUILDSPEC ?= test/integration/codebuild + .PHONY: test-integ test-integ: setup-codebuild-agent - CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_all.sh test/integration/codebuild + CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_all.sh $(BUILDSPEC) # Command to run everytime you make changes to verify everything works .PHONY: dev diff --git a/aws-lambda-java-runtime-interface-client/README.md b/aws-lambda-java-runtime-interface-client/README.md index b72a6238c..a49bf87b4 100644 --- a/aws-lambda-java-runtime-interface-client/README.md +++ b/aws-lambda-java-runtime-interface-client/README.md @@ -11,7 +11,7 @@ You can include this package in your preferred base image to make that base imag ### Creating a Docker Image for Lambda with the Runtime Interface Client -Choose a preferred base image. The Runtime Interface Client is tested on Amazon Linux, Alpine, Ubuntu, Debian, and CentOS. The requirements are that the image is: +Choose a preferred base image. The Runtime Interface Client is tested on Amazon Linux, Alpine, Ubuntu, and Debian. The requirements are that the image is: * built for x86_64 and ARM64 * contains Java >= 8 diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc index 7ad20122c..ab6f83b69 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.glibc @@ -7,7 +7,6 @@ ARG AWS_REGION RUN if [ -n "${AWS_REGION}" ]; then echo "${AWS_REGION}" > /etc/yum/vars/awsregion; fi RUN yum install -y \ - cmake3 \ tar \ gzip \ make \ @@ -33,18 +32,12 @@ RUN ./configure \ make && \ make install -# Install aws-lambda-cpp dependency -ADD ./deps/aws-lambda-cpp-* /src/deps/aws-lambda-cpp -RUN mkdir -p /src/deps/aws-lambda-cpp/build -WORKDIR /src/deps/aws-lambda-cpp/build -RUN cmake3 .. \ - -DENABLE_LTO=OFF \ - -DCMAKE_CXX_FLAGS="-fPIC -DBACKWARD_SYSTEM_UNKNOWN" \ - -DCMAKE_CXX_STANDARD=11 \ - -DCMAKE_INSTALL_PREFIX=$(pwd)/../../artifacts \ - -DCMAKE_MODULE_PATH=$(pwd)/../../artifacts/lib/pkgconfig && \ - make && \ - make install +# Install prebuilt aws-lambda-cpp dependency. The static library and headers +# were fetched and GPG-verified on the host by build-jni-lib.sh; here we only +# COPY them into the artifacts tree the native client links against (the build +# container never reaches the network). +COPY ./deps/aws-lambda-cpp/include /src/deps/artifacts/include +COPY ./deps/aws-lambda-cpp/lib/libaws-lambda-runtime.a /src/deps/artifacts/lib/ # Build native client ADD *.cpp *.h /src/ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl index 5fd7f4882..fa5b98173 100644 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/Dockerfile.musl @@ -6,7 +6,6 @@ ARG CURL_VERSION RUN apk update && \ apk add \ openjdk11 \ - cmake \ file \ g++ \ gcc \ @@ -31,17 +30,8 @@ RUN ./configure \ make && \ make install -# Install aws-lambda-cpp dependency -ADD ./deps/aws-lambda-cpp-* /src/deps/aws-lambda-cpp -RUN mkdir -p /src/deps/aws-lambda-cpp/build -WORKDIR /src/deps/aws-lambda-cpp/build -RUN cmake .. \ - -DCMAKE_CXX_FLAGS="-fPIC -DBACKWARD_SYSTEM_UNKNOWN" \ - -DCMAKE_CXX_STANDARD=11 \ - -DCMAKE_INSTALL_PREFIX=$(pwd)/../../artifacts\ - -DCMAKE_MODULE_PATH=$(pwd)/../../artifacts/lib/pkgconfig && \ - make && \ - make install +COPY ./deps/aws-lambda-cpp/include /src/deps/artifacts/include +COPY ./deps/aws-lambda-cpp/lib/libaws-lambda-runtime.a /src/deps/artifacts/lib/ # Build native client ADD *.cpp *.h /src/ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh index 5323030fa..28263531b 100755 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh +++ b/aws-lambda-java-runtime-interface-client/src/main/jni/build-jni-lib.sh @@ -9,12 +9,67 @@ MULTI_ARCH=${2} BUILD_OS=${3} BUILD_ARCH=${4} CURL_VERSION=7.83.1 -# Registry hosting the base images. Defaults to public.ecr.aws for local and -# GitHub-hosted builds; the release workflow overrides it with the ECR -# pull-through cache so egress-locked runners don't hit public.ecr.aws. + BASE_REGISTRY="${BASE_REGISTRY:-public.ecr.aws}" AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-}}" +# aws-lambda-cpp is consumed as the prebuilt static library published on the +# upstream GitHub release rather than being compiled from a vendored source +# tree. We fetch and GPG-verify it +ALC_VERSION="1.0.1" +ALC_TAG="v${ALC_VERSION}" +ALC_REPO_URL="https://github.com/awslabs/aws-lambda-cpp" +ALC_RELEASE_URL="${ALC_REPO_URL}/releases/download/${ALC_TAG}" +ALC_SIGNING_KEY_URL="https://raw.githubusercontent.com/awslabs/aws-lambda-cpp/${ALC_TAG}/signing-public-key.asc" +ALC_STAGE_DIR="${SRC_DIR}/deps/aws-lambda-cpp" + +function fetch_aws_lambda_cpp() { + arch=$1 + + release_arch="${arch/aarch_64/aarch64}" + + if [ -f "${ALC_STAGE_DIR}/.staged-arch" ] && \ + [ "$(cat "${ALC_STAGE_DIR}/.staged-arch")" == "${release_arch}" ]; then + echo "aws-lambda-cpp ${ALC_VERSION} (${release_arch}) already staged, skipping fetch" + return + fi + + echo "Fetching prebuilt aws-lambda-cpp ${ALC_VERSION} for ${release_arch}" + rm -rf "${ALC_STAGE_DIR}" + mkdir -p "${ALC_STAGE_DIR}/lib" "${ALC_STAGE_DIR}/include" + + local workdir + workdir=$(mktemp -d) + local lib_asset="libaws-lambda-runtime-${release_arch}.a" + + curl -fsSL -o "${workdir}/${lib_asset}" "${ALC_RELEASE_URL}/${lib_asset}" + curl -fsSL -o "${workdir}/${lib_asset}.asc" "${ALC_RELEASE_URL}/${lib_asset}.asc" + curl -fsSL -o "${workdir}/SHA256SUMS" "${ALC_RELEASE_URL}/SHA256SUMS" + curl -fsSL -o "${workdir}/SHA256SUMS.asc" "${ALC_RELEASE_URL}/SHA256SUMS.asc" + curl -fsSL -o "${workdir}/signing-key.asc" "${ALC_SIGNING_KEY_URL}" + + local gnupghome + gnupghome=$(mktemp -d) + gpg --homedir "${gnupghome}" --batch --quiet --import "${workdir}/signing-key.asc" + gpg --homedir "${gnupghome}" --batch --verify "${workdir}/${lib_asset}.asc" "${workdir}/${lib_asset}" + gpg --homedir "${gnupghome}" --batch --verify "${workdir}/SHA256SUMS.asc" "${workdir}/SHA256SUMS" + rm -rf "${gnupghome}" + + # Cross-check the checksum too (defence in depth; SHA256SUMS is itself signed). + ( cd "${workdir}" && grep "${lib_asset}\$" SHA256SUMS | sha256sum -c - ) + + cp "${workdir}/${lib_asset}" "${ALC_STAGE_DIR}/lib/libaws-lambda-runtime.a" + + # Headers aren't a release asset, so take them from the source at the same + # tag. They are declarations only -- every symbol lives in the prebuilt lib. + curl -fsSL -o "${workdir}/src.tar.gz" "${ALC_REPO_URL}/archive/refs/tags/${ALC_TAG}.tar.gz" + tar -xzf "${workdir}/src.tar.gz" -C "${workdir}" "aws-lambda-cpp-${ALC_VERSION}/include" + cp -R "${workdir}/aws-lambda-cpp-${ALC_VERSION}/include/." "${ALC_STAGE_DIR}/include/" + + echo "${release_arch}" > "${ALC_STAGE_DIR}/.staged-arch" + rm -rf "${workdir}" +} + function get_docker_platform() { arch=$1 @@ -44,6 +99,8 @@ function build_for_libc_arch() { arch=$2 artifact=$3 + fetch_aws_lambda_cpp "${arch}" + docker_platform=$(get_docker_platform ${arch}) echo "Compiling the native library with libc implementation \`${libc_impl}\` on architecture \`${arch}\` using Docker platform \`${docker_platform}\`" @@ -118,12 +175,10 @@ else declare -a ARCHITECTURES=("x86_64" "aarch_64") declare -a LIBC_IMPLS=("glibc" "musl") - # `arch` reports the host as `aarch64`, but we use Maven's classifier - # spelling `aarch_64` in ARCHITECTURES, so normalize before comparing. - host_arch=$(arch) - if [ "${host_arch}" == "aarch64" ]; then - host_arch="aarch_64" - fi + host_arch="$(arch)" + case "${host_arch}" in + aarch64|arm64) host_arch="aarch_64" ;; + esac for arch in "${ARCHITECTURES[@]}"; do diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-format b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-format deleted file mode 100644 index ec8bb67d4..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-format +++ /dev/null @@ -1,61 +0,0 @@ ---- -Language: Cpp -# BasedOnStyle: Mozilla -AlignAfterOpenBracket: AlwaysBreak -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlines: Right -AlignOperands: true -AlignTrailingComments: true -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Inline -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: true -BinPackArguments: false -BinPackParameters: false -BreakBeforeBinaryOperators: None -BreakBeforeTernaryOperators: true -BreakStringLiterals: true -ColumnLimit: 120 -ContinuationIndentWidth: 4 -DerivePointerAlignment: false -IncludeBlocks: Preserve -IndentCaseLabels: true -IndentPPDirectives: AfterHash -IndentWidth: 4 -IndentWrappedFunctionNames: true -KeepEmptyLinesAtTheStartOfBlocks: true -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -PenaltyBreakComment: 10 -PenaltyBreakAssignment: 20 -PenaltyBreakString: 30 -PenaltyBreakBeforeFirstCallParameter: 35 -PenaltyBreakFirstLessLess: 40 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 100000 -PointerAlignment: Left -ReflowComments: true -SortIncludes: false -SpaceAfterCStyleCast: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Cpp11 -TabWidth: 4 -UseTab: Never -NamespaceIndentation: None -BreakBeforeBraces: Stroustrup -AccessModifierOffset: -4 -... - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-tidy b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-tidy deleted file mode 100644 index 7d343ead8..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.clang-tidy +++ /dev/null @@ -1,41 +0,0 @@ ---- -Checks: -'clang-diagnostic-*,clang-analyzer-*,performance-*,readability-*,modernize-*,bugprone-*,misc-*,-modernize-use-trailing-return-type' -WarningsAsErrors: '*' -HeaderFilterRegex: 'include/aws/.*\.h$' -FormatStyle: 'none' -CheckOptions: - - key: modernize-pass-by-value.ValuesOnly - value: '1' - - key: readability-implicit-bool-conversion.AllowPointerConditions - value: '1' - - key: readability-implicit-bool-conversion.AllowIntegerConditions - value: '1' - - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic - value: '1' - - key: readability-identifier-naming.ClassCase - value: 'lower_case' - - key: readability-identifier-naming.StructCase - value: 'lower_case' - - key: readability-identifier-naming.StructCase - value: 'lower_case' - - key: readability-identifier-naming.ParameterCase - value: 'lower_case' - - key: readability-identifier-naming.PrivateMemberCase - value: 'lower_case' - - key: readability-identifier-naming.LocalVariableCase - value: 'lower_case' - - key: readability-identifier-naming.TypeAliasCase - value: 'lower_case' - - key: readability-identifier-naming.UnionCase - value: 'lower_case' - - key: readability-identifier-naming.FunctionCase - value: 'lower_case' - - key: readability-identifier-naming.NamespaceCase - value: 'lower_case' - - key: readability-identifier-naming.GlobalConstantCase - value: 'UPPER_CASE' - - key: readability-identifier-naming.PrivateMemberPrefix - value: 'm_' - -... diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.github/PULL_REQUEST_TEMPLATE.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ab40d21d7..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -*Issue #, if available:* - -*Description of changes:* - - -By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.gitignore b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.gitignore deleted file mode 100644 index 647f44937..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build -tags -TODO -compile_commands.json -.clangd diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CMakeLists.txt deleted file mode 100644 index 1765caf06..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CMakeLists.txt +++ /dev/null @@ -1,132 +0,0 @@ -cmake_minimum_required(VERSION 3.9) -set(CMAKE_CXX_STANDARD 11) -project(aws-lambda-runtime - VERSION 0.2.7 - LANGUAGES CXX) - -option(ENABLE_LTO "Enables link-time optimization, requires compiler support." ON) -option(ENABLE_TESTS "Enables building the test project, requires AWS C++ SDK." OFF) - -add_library(${PROJECT_NAME} - "src/logging.cpp" - "src/runtime.cpp" - "src/backward.cpp" - "${CMAKE_CURRENT_BINARY_DIR}/version.cpp" - ) - -set_target_properties(${PROJECT_NAME} PROPERTIES - SOVERSION 0 - VERSION ${PROJECT_VERSION}) - -target_include_directories(${PROJECT_NAME} PUBLIC - $ - $) - -if (ENABLE_LTO) - include(CheckIPOSupported) - check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) - if(has_lto) - set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) - else() - message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") - endif() -endif() - -find_package(CURL REQUIRED) -if (CMAKE_VERSION VERSION_LESS 3.12) - target_link_libraries(${PROJECT_NAME} PRIVATE ${CURL_LIBRARIES}) -else() - target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl) -endif() - -target_include_directories(${PROJECT_NAME} PRIVATE ${CURL_INCLUDE_DIRS}) - -target_compile_options(${PROJECT_NAME} PRIVATE - "-fno-exceptions" - "-fno-rtti" - "-fvisibility=hidden" - "-fvisibility-inlines-hidden" - "-Wall" - "-Wextra" - "-Werror" - "-Wconversion" - "-Wno-sign-conversion") - -find_library(DW_LIB NAMES dw) -if (NOT DW_LIB STREQUAL DW_LIB-NOTFOUND) - message("-- Enhanced stack-traces are enabled via libdw: ${DW_LIB}") - target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_DW=1") - target_link_libraries(${PROJECT_NAME} PUBLIC "${DW_LIB}") -else() - find_library(BFD_LIB NAMES bfd) - if (NOT BFD_LIB STREQUAL BFD_LIB-NOTFOUND) - message("-- Enhanced stack-traces are enabled via libbfd: ${BFD_LIB}") - target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_BFD=1") - target_link_libraries(${PROJECT_NAME} PRIVATE "${BFD_LIB}") - endif() -endif() - -if (LOG_VERBOSITY) - target_compile_definitions(${PROJECT_NAME} PRIVATE "AWS_LAMBDA_LOG=${LOG_VERBOSITY}") -elseif(CMAKE_BUILD_TYPE STREQUAL Debug) - target_compile_definitions(${PROJECT_NAME} PRIVATE "AWS_LAMBDA_LOG=3") -else () - target_compile_definitions(${PROJECT_NAME} PRIVATE "AWS_LAMBDA_LOG=0") -endif() - -#tests -if (ENABLE_TESTS) - enable_testing() - add_subdirectory(tests) -endif() - -#versioning -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/src/version.cpp.in" - "${CMAKE_CURRENT_BINARY_DIR}/version.cpp" - NEWLINE_STYLE LF) - -include (CMakePackageConfigHelpers) - -write_basic_package_version_file("${PROJECT_NAME}-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - -# installation -install(FILES "include/aws/http/response.h" - DESTINATION "include/aws/http") - -install(FILES - "include/aws/lambda-runtime/runtime.h" - "include/aws/lambda-runtime/version.h" - "include/aws/lambda-runtime/outcome.h" - DESTINATION "include/aws/lambda-runtime") - -install(FILES "include/aws/logging/logging.h" - DESTINATION "include/aws/logging") - -include(GNUInstallDirs) -install(TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}-targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) - -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" - @ONLY) - -export(EXPORT "${PROJECT_NAME}-targets" NAMESPACE AWS::) - -install(EXPORT "${PROJECT_NAME}-targets" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/" - NAMESPACE AWS::) - -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/") - -install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/packaging/packager" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/") - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CODE_OF_CONDUCT.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CODE_OF_CONDUCT.md deleted file mode 100644 index 3b6446687..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,4 +0,0 @@ -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CONTRIBUTING.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CONTRIBUTING.md deleted file mode 100644 index e8c3aa58e..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/CONTRIBUTING.md +++ /dev/null @@ -1,61 +0,0 @@ -# Contributing Guidelines - -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. - -Please read through this document before submitting any issues or pull requests to ensure we have all the necessary -information to effectively respond to your bug report or contribution. - - -## Reporting Bugs/Feature Requests - -We welcome you to use the GitHub issue tracker to report bugs or suggest features. - -When filing an issue, please check [existing open](https://github.com/awslabs/aws-lambda-cpp-runtime/issues), or [recently closed](https://github.com/awslabs/aws-lambda-cpp-runtime/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: - -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment - - -## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: - -1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. - -To send us a pull request, please: - -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. - -GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and -[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). - - -## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-lambda-cpp-runtime/labels/help%20wanted) issues is a great place to start. - - -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. - - -## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. - - -## Licensing - -See the [LICENSE](https://github.com/awslabs/aws-lambda-cpp-runtime/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. - -We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/LICENSE b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/NOTICE b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/NOTICE deleted file mode 100644 index 34e186a0d..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ -AWS Lambda Cpp Runtime -Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/README.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/README.md deleted file mode 100644 index 0812476a0..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/README.md +++ /dev/null @@ -1,220 +0,0 @@ -[![GitHub](https://img.shields.io/github/license/awslabs/aws-lambda-cpp.svg)](https://github.com/awslabs/aws-lambda-cpp/blob/master/LICENSE) -![CodeBuild](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiQkN1b0srbWtnUjNibFVyL2psNmdaM0l4RnVQNzVBeG84QnQvUjRmOEJVdXdHUXMxZ25iWnFZQUtGTkUxVGJhcGZaVEhXY2JOSTFHTlkvaGF2RDRIZlpVPSIsIml2UGFyYW1ldGVyU3BlYyI6IjRiS3hlRjFxVFZHSWViQmQiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master) -[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/awslabs/aws-lambda-cpp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/awslabs/aws-lambda-cpp/context:cpp) -## AWS Lambda C++ Runtime - -C++ implementation of the lambda runtime API - -## Design Goals -1. Negligible cold-start overhead (single digit millisecond). -2. Freedom of choice in compilers, build platforms and C standard library versions. - -## Building and Installing the Runtime -Since AWS Lambda runs on GNU/Linux, you should build this runtime library and your logic on GNU/Linux as well. - -### Prerequisites -Make sure you have the following packages installed first: -1. CMake (version 3.9 or later) -1. git -1. Make or Ninja -1. zip -1. libcurl-devel (on Debian-basded distros it's libcurl4-openssl-dev) - -In a terminal, run the following commands: -```bash -$ git clone https://github.com/awslabs/aws-lambda-cpp.git -$ cd aws-lambda-cpp -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install -$ make && make install -``` - -To consume this library in a project that is also using CMake, you would do: - -```cmake -cmake_minimum_required(VERSION 3.9) -set(CMAKE_CXX_STANDARD 11) -project(demo LANGUAGES CXX) -find_package(aws-lambda-runtime) -add_executable(${PROJECT_NAME} "main.cpp") -target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime) -target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") -target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra") - -# this line creates a target that packages your binary and zips it up -aws_lambda_package_target(${PROJECT_NAME}) -``` - -And here is how a sample `main.cpp` would look like: -```cpp -#include - -using namespace aws::lambda_runtime; - -static invocation_response my_handler(invocation_request const& req) -{ - if (req.payload.length() > 42) { - return invocation_response::failure("error message here"/*error_message*/, - "error type here" /*error_type*/); - } - - return invocation_response::success("json payload here" /*payload*/, - "application/json" /*MIME type*/); -} - -int main() -{ - run_handler(my_handler); - return 0; -} -``` - -And finally, here's how you would package it all. Run the following commands from your application's root directory: - -```bash -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/lambda-install -$ make -$ make aws-lambda-package-demo -``` -The last command above `make aws-lambda-package-demo` will create a zip file called `demo.zip` in the current directory. - -Now, create an IAM role and the Lambda function via the AWS CLI. - -First create the following trust policy JSON file - -``` -$ cat trust-policy.json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": ["lambda.amazonaws.com"] - }, - "Action": "sts:AssumeRole" - } - ] -} - -``` -Then create the IAM role: - -```bash -$ aws iam create-role --role-name lambda-demo --assume-role-policy-document file://trust-policy.json -``` - -Note down the role Arn returned to you after running that command. We'll need it in the next steps: - -Attach the following policy to allow Lambda to write logs in CloudWatch: -```bash -$ aws iam attach-role-policy --role-name lambda-demo --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole -``` - -Make sure you attach the appropriate policies and/or permissions for any other AWS services that you plan on using. - -And finally, create the Lambda function: - -``` -$ aws lambda create-function --function-name demo \ ---role \ ---runtime provided --timeout 15 --memory-size 128 \ ---handler demo --zip-file fileb://demo.zip -``` - -And to invoke the function: -```bash -$ aws lambda invoke --function-name demo --payload '{"answer":42}' output.txt -``` - -## Using the C++ SDK for AWS with this runtime -This library is completely independent from the AWS C++ SDK. You should treat the AWS C++ SDK as just another dependency in your application. -See [the examples section](https://github.com/awslabs/aws-lambda-cpp/tree/master/examples/) for a demo utilizing the AWS C++ SDK with this Lambda runtime. - -## Supported Compilers -Any *fully* compliant C++11 compiler targeting GNU/Linux x86-64 should work. Please avoid compiler versions that provide half-baked C++11 support. - -- Use GCC v5.x or above -- 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. - -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. - -To ensure that your application will run correctly on Lambda, we must package the entire C runtime library with your function. -If you choose to build on the same [Amazon Linux version used by lambda](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html), you can avoid packaging the C runtime in your zip file. -This can be done by passing the `NO_LIBC` flag in CMake as follows: - -```cmake -aws_lambda_package_target(${PROJECT_NAME} NO_LIBC) -``` -### Common Pitfalls with Packaging - -* Any library dependency your Lambda function has that is dynamically loaded via `dlopen` will NOT be automatically packaged. You **must** add those dependencies manually to the zip file. -This applies to any configuration or resource files that your code depends on. - -* If you are making HTTP calls over TLS (https), keep in mind that the CA bundle location is different between distros. -For example, if you are using the AWS C++ SDK, it's best to set the following configuration options: - -```cpp -Aws::Client::ClientConfiguration config; -config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; -``` -If you are not using the AWS C++ SDK, but happen to be using libcurl directly, you can set the CA bundle location by doing: -```c -curl_easy_setopt(curl_handle, CURLOPT_CAINFO, "/etc/pki/tls/certs/ca-bundle.crt"); -``` - -## FAQ & Troubleshooting -1. **Why is the zip file so large? what are all those files?** - Typically, the zip file is large because we have to package the entire C standard library. - You can reduce the size by doing some or all of the following: - - Ensure you're building in release mode `-DCMAKE_BUILD_TYPE=Release` - - If possible, build your function using musl libc, it's tiny. The easiest way to do this, assuming your code is portable, is to build on Alpine linux, which uses musl libc by default. -1. **How to upload a zip file that's bigger than 50MB via the CLI?** - Upload your zip file to S3 first: - ```bash - $ aws s3 cp demo.zip s3://mys3bucket/demo.zip - ``` - NOTE: you must use the same region for your S3 bucket as the lambda. - - Then you can create the Lambda function this way: - - ```bash - $ aws lambda create-function --function-name demo \ - --role \ - --runtime provided --timeout 15 --memory-size 128 \ - --handler demo - --code "S3Bucket=mys3bucket,S3Key=demo.zip" - ``` -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). - - To get a more detailed stack-trace with source-code information such as line numbers, file names, etc. you need to install one of the following packages: - - On Debian-based systems - `sudo apt install libdw-dev` or `sudo apt install binutils-dev` - - On RHEL based systems - `sudo yum install elfutils-devel` or `sudo yum install binutils-devel` - If you have either of those packages installed, CMake will detect them and automatically link to them. No other - steps are required. - - Turn up the logging verbosity to the maximum. - - Build the runtime in Debug mode. `-DCMAKE_BUILD_TYPE=Debug`. Verbose logs are enabled by default in Debug builds. - - To enable verbose logs in Release builds, build the runtime with the following CMake flag `-DLOG_VERBOSITY=3` - - If you are using the AWS C++ SDK, see [this FAQ](https://github.com/aws/aws-sdk-cpp/wiki#how-do-i-turn-on-logging) on how to adjust its logging verbosity - - Run your code locally on an Amazon Linux AMI or Docker container to reproduce the problem - - If you go the AMI route, [use the official one](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) recommended by AWS Lambda - - If you go the Docker route, use the following command to launch a container running AL2017.03 - `$ docker run -v /tmp:/tmp -it --security-opt seccomp=unconfined amazonlinux:2017.03` - The `security-opt` argument is necessary to run `gdb`, `strace`, etc. -1. **CURL problem with the SSL CA cert** - - Make sure you are using a `libcurl` version built with OpenSSL, or one of its flavors (BoringSSL, LibreSSL) - - Make sure you tell `libcurl` where to find the CA bundle file. - - You can try hitting the non-TLS version of the endpoint if available. (Not Recommended). -1. **No known conversion between `std::string` and `Aws::String`** - - Either turn off custom memory management in the AWS C++ SDK or build it as a static library (`-DBUILD_SHARED_LIBS=OFF`) - -## License - -This library is licensed under the Apache 2.0 License. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/amazonlinux-2017.03.yml b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/amazonlinux-2017.03.yml deleted file mode 100644 index eab1bafb5..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/amazonlinux-2017.03.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 0.1 -# This uses the docker image specified in ci/docker/amazon-linux-2017.03 -phases: - pre_build: - commands: - - alias cmake=cmake3 - - pip install awscli - - ci/codebuild/build-cpp-sdk.sh - build: - commands: - - echo Build started on `date` - - ci/codebuild/build.sh -DENABLE_TESTS=ON -DTEST_RESOURCE_PREFIX=amzn201703 - - ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun amzn201703 - - ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun-no-glibc amzn201703 - post_build: - commands: - - echo Build completed on `date` - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build-cpp-sdk.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build-cpp-sdk.sh deleted file mode 100755 index 93ae7ebec..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build-cpp-sdk.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -# build the AWS C++ SDK -cd /aws-sdk-cpp -git pull -mkdir build -cd build -cmake .. -GNinja -DBUILD_ONLY="lambda" \ - -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_UNITY_BUILD=ON \ - -DBUILD_SHARED_LIBS=ON \ - -DENABLE_TESTING=OFF \ - -DCMAKE_INSTALL_PREFIX=/install $@ -ninja -ninja install diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build.sh deleted file mode 100755 index 53a9544e2..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/build.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -# build the lambda-runtime -cd $CODEBUILD_SRC_DIR -mkdir build -cd build -cmake .. -GNinja -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/install $@ -ninja -ninja install diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/format-check.sh b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/format-check.sh deleted file mode 100755 index 3afb80230..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/ci/codebuild/format-check.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -CLANG_FORMAT=clang-format - -if NOT type $CLANG_FORMAT > /dev/null 2>&1; then - echo "No appropriate clang-format found." - exit 1 -fi - -FAIL=0 -SOURCE_FILES=$(find src include tests -type f -name "*.h" -o -name "*.cpp") -for i in $SOURCE_FILES -do - if [ $($CLANG_FORMAT -output-replacements-xml $i | grep -c " - DEPENDS ${target}) -endfunction() - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/Dockerfile b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/Dockerfile deleted file mode 100644 index aabb4dd42..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM alpine:latest - -RUN apk update && apk add cmake make git g++ bash curl-dev zlib-dev diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/CMakeLists.txt deleted file mode 100644 index 02da6ccf6..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -set(CMAKE_CXX_STANDARD 11) - -project(api LANGUAGES CXX) - -find_package(aws-lambda-runtime REQUIRED) -find_package(AWSSDK COMPONENTS core) - -add_executable(${PROJECT_NAME} "main.cpp") -target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES}) - -aws_lambda_package_target(${PROJECT_NAME}) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/README.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/README.md deleted file mode 100644 index d184165b6..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Example using the AWS C++ Lambda runtime and Amazon API Gateway - -In this example, we'll build a simple "Hello, World" lambda function that can be invoked using an api endpoint created using Amazon API gateway. This example can be viewed as the C++ counterpart to the NodeJS "Hello, World" API example as viewed [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). At the end of this example, you should be able to invoke your lambda via an api endpoint and receive a raw JSON response. This example employs the use of the AWS C++ SDK to parse the request and write the necessary response. - -## Build the AWS C++ SDK -Start by building the SDK from source. - -```bash -$ mkdir ~/install -$ git clone https://github.com/aws/aws-sdk-cpp.git -$ cd aws-sdk-cpp -$ mkdir build -$ cd build -$ cmake .. -DBUILD_ONLY="core" \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DENABLE_UNITY_BUILD=ON \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ - -DENABLE_UNITY_BUILD=ON -$ make -$ make install -``` - -## Build the Runtime -We need to build the C++ Lambda runtime as outlined in the other examples. - -```bash -$ git clone https://github.com/awslabs/aws-lambda-cpp-runtime.git -$ cd aws-lambda-cpp-runtime -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ -$ make -$ make install -``` - -## Build the application -The next step is to build the Lambda function in `main.cpp` and run the packaging command as follows: - -```bash -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install -$ make -$ make aws-lambda-package-api -``` - -You should now have a zip file called `api.zip`. Follow the instructions in the main README to upload it and return here once complete. - -## Using Amazon API Gateway -For the rest of this example, we will use the AWS Management Console to create the API endpoint using Amazon API Gateway. - -1. Navigate to AWS Lambda within the console [here](https://console.aws.amazon.com/lambda/home) -1. Select the newly created function. Within the specific function, the "Designer" window should appear. -1. Simply click "Add trigger" -> "API Gateway" -> "Create an API". Please view the settings below. - * API Type: HTTP API - * Security: Open - * API name: Hello-World-API (or desired name) - * Deployment stage: default -1. Once you have added the API gateway, locate the newly created endpoint. View how to test the endpoint below. - -## Test the endpoint -Feel free to test the endpoint any way you desire. Below is a way to test using cURL: - -``` -curl -v -X POST \ - '?name=Bradley&city=Chicago' \ - -H 'content-type: application/json' \ - -H 'day: Sunday' \ - -d '{ "time": "evening" }' -``` - -With the expected response being: -``` -{ - "message": "Good evening, Bradley of Chicago. Happy Sunday!" -} -``` diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/main.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/main.cpp deleted file mode 100644 index 90f103551..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/api-gateway/main.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include - -using namespace aws::lambda_runtime; - -invocation_response my_handler(invocation_request const& request) -{ - - using namespace Aws::Utils::Json; - - JsonValue json(request.payload); - if (!json.WasParseSuccessful()) { - return invocation_response::failure("Failed to parse input JSON", "InvalidJSON"); - } - - auto v = json.View(); - Aws::SimpleStringStream ss; - ss << "Good "; - - if (v.ValueExists("body") && v.GetObject("body").IsString()) { - auto body = v.GetString("body"); - JsonValue body_json(body); - - if (body_json.WasParseSuccessful()) { - auto body_v = body_json.View(); - ss << (body_v.ValueExists("time") && body_v.GetObject("time").IsString() ? body_v.GetString("time") : ""); - } - } - ss << ", "; - - if (v.ValueExists("queryStringParameters")) { - auto query_params = v.GetObject("queryStringParameters"); - ss << (query_params.ValueExists("name") && query_params.GetObject("name").IsString() - ? query_params.GetString("name") - : "") - << " of "; - ss << (query_params.ValueExists("city") && query_params.GetObject("city").IsString() - ? query_params.GetString("city") - : "") - << ". "; - } - - if (v.ValueExists("headers")) { - auto headers = v.GetObject("headers"); - ss << "Happy " - << (headers.ValueExists("day") && headers.GetObject("day").IsString() ? headers.GetString("day") : "") - << "!"; - } - - JsonValue resp; - resp.WithString("message", ss.str()); - - return invocation_response::success(resp.View().WriteCompact(), "application/json"); -} - -int main() -{ - run_handler(my_handler); - return 0; -} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/CMakeLists.txt deleted file mode 100644 index 8447e0197..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -set(CMAKE_CXX_STANDARD 11) -project(ddb-demo LANGUAGES CXX) - -find_package(aws-lambda-runtime) -find_package(AWSSDK COMPONENTS dynamodb) - -add_executable(${PROJECT_NAME} "main.cpp") - -target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES}) - -target_compile_options(${PROJECT_NAME} PRIVATE - "-fno-exceptions" - "-fno-rtti" - "-Wall" - "-Wextra" - "-Werror" - "-Wconversion" - "-Wno-sign-conversion") - -target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") - -aws_lambda_package_target(${PROJECT_NAME}) - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/README.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/README.md deleted file mode 100644 index db84fd87e..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Example using the AWS C++ SDK with Lambda & DynamoDB - -We'll build a Lambda function that can be used as an API Gateway proxy to fetch records from a DynamoDB table. -To also show case how this can be done on a Linux distro other than Amazon Linux, you can use the Dockerfile in this directory to create an Alpine Linux environment in which you can run the following instructions. - -That being said, the instructions below should work on any Linux distribution. - -## Build the AWS C++ SDK -Start by building the SDK from source. -```bash -$ mkdir ~/install -$ git clone https://github.com/aws/aws-sdk-cpp.git -$ cd aws-sdk-cpp -$ mkdir build -$ cd build -$ cmake .. -DBUILD_ONLY="dynamodb" \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DENABLE_UNITY_BUILD=ON \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ - -DENABLE_UNITY_BUILD=ON - -$ make -j 4 -$ make install -``` - -## Build the Runtime -Now let's build the C++ Lambda runtime, so in a separate directory clone this repository and follow these steps: - -```bash -$ git clone https://github.com/awslabs/aws-lambda-cpp-runtime.git -$ cd aws-lambda-cpp-runtime -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ -$ make -$ make install -``` - -## Build the application -The last step is to build the Lambda function in `main.cpp` and run the packaging command as follows: - -```bash -$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install -$ make -$ make aws-lambda-package-ddb-demo -``` - -You should now have a zip file called `ddb-demo.zip`. Follow the instructions in the main README to upload it and invoke the lambda. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/main.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/main.cpp deleted file mode 100644 index a8b86621a..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/dynamodb/main.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// API Gateway Input format -// { -// "resource": "Resource path", -// "path": "Path parameter", -// "httpMethod": "Incoming request's method name" -// "headers": {String containing incoming request headers} -// "multiValueHeaders": {List of strings containing incoming request headers} -// "queryStringParameters": {query string parameters } -// "multiValueQueryStringParameters": {List of query string parameters} -// "pathParameters": {path parameters} -// "stageVariables": {Applicable stage variables} -// "requestContext": {Request context, including authorizer-returned key-value pairs} -// "body": "A JSON string of the request payload." -// "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode" -// } - -static char const TAG[] = "lambda"; - -struct criteria { - criteria(Aws::Utils::Json::JsonView data) : error_msg(nullptr) - { - using namespace Aws::Utils; - auto path_params = data.GetObject("pathParameters"); - if (!path_params.ValueExists("productId")) { - error_msg = "Missing URL parameter {productId}."; - return; - } - - product_id = path_params.GetString("productId"); - auto qs = data.GetObject("queryStringParameters"); - - if (!qs.ValueExists("startDate")) { - error_msg = "Missing query string parameter 'startDate'."; - return; - } - start_date = DateTime(qs.GetString("startDate"), DateFormat::ISO_8601); - if (!start_date.WasParseSuccessful()) { - error_msg = "Invalid input format. startDate must be in ISO 8601 format."; - return; - } - - if (!qs.ValueExists("endDate")) { - error_msg = "Missing query string parameter 'endDate'."; - return; - } - end_date = DateTime(qs.GetString("endDate"), DateFormat::ISO_8601); - if (!end_date.WasParseSuccessful()) { - error_msg = "Invalid input format. endDate must be in ISO 8601 format."; - return; - } - } - - std::string product_id; - Aws::Utils::DateTime start_date; - Aws::Utils::DateTime end_date; - char const* error_msg; -}; - -Aws::Utils::Json::JsonValue query(criteria const cr, Aws::DynamoDB::DynamoDBClient const& client) -{ - using namespace Aws::DynamoDB; - using namespace Aws::DynamoDB::Model; - using namespace Aws::Utils::Json; - - AWS_LOGSTREAM_DEBUG( - TAG, - "criteria is: product_id: " << cr.product_id << " start_date epoch: " << cr.start_date.Millis() - << " end_date epoch: " << cr.end_date.Millis()); - - QueryRequest query; - - auto const& table_name = Aws::Environment::GetEnv("TABLE_NAME"); - query.SetTableName(table_name); - query.SetKeyConditionExpression("#H = :h AND #R BETWEEN :s AND :e"); - query.AddExpressionAttributeNames("#H", "product_id"); - query.AddExpressionAttributeNames("#R", "date_time"); - - query.AddExpressionAttributeValues(":h", AttributeValue(cr.product_id)); - AttributeValue date; - date.SetN(std::to_string(cr.start_date.Millis() / 1000)); - query.AddExpressionAttributeValues(":s", date); - - date.SetN(std::to_string(cr.end_date.Millis() / 1000)); - query.AddExpressionAttributeValues(":e", date); - - auto outcome = client.Query(query); - if (outcome.IsSuccess()) { - auto const& maps = outcome.GetResult().GetItems(); // returns vector of map - if (maps.empty()) { - AWS_LOGSTREAM_DEBUG(TAG, "No data returned from query"); - return {}; - } - - // Schema - // string_attr :product_id, hash_key: true - // epoch_time_attr :date_time, range_key: true - // string_attr :product_title - // string_attr :marketplace - // string_attr :product_category - // date_attr :review_date - // integer_attr :star_rating - // float_attr :postive - // float_attr :mixed - // float_attr :neutral - // float_attr :negative - - JsonValue output; - output.WithString("product", maps[0].find("product_title")->second.GetS()); - output.WithString("category", maps[0].find("product_category")->second.GetS()); - Aws::Utils::Array sentiments(maps.size()); - for (size_t i = 0; i < maps.size(); i++) { - JsonValue review; - auto&& m = maps[i]; - - auto it = m.find("review_date"); - if (it != m.end()) { - review.WithString("date", it->second.GetS()); - } - - it = m.find("positive"); - if (it != m.end()) { - review.WithString("positive", it->second.GetN()); - } - - it = m.find("negative"); - if (it != m.end()) { - review.WithString("negative", it->second.GetN()); - } - - it = m.find("mixed"); - if (it != m.end()) { - review.WithString("mixed", it->second.GetN()); - } - - it = m.find("neutral"); - if (it != m.end()) { - review.WithString("neutral", it->second.GetN()); - } - - sentiments[i] = std::move(review); - } - output.WithArray("sentiment", sentiments); - return output; - } - - AWS_LOGSTREAM_ERROR(TAG, "database query failed: " << outcome.GetError()); - return {}; -} - - -aws::lambda_runtime::invocation_response my_handler( - aws::lambda_runtime::invocation_request const& req, - Aws::DynamoDB::DynamoDBClient const& client) -{ - using namespace Aws::Utils::Json; - AWS_LOGSTREAM_DEBUG(TAG, "received payload: " << req.payload); - JsonValue eventJson(req.payload); - assert(eventJson.WasParseSuccessful()); - const criteria cr(eventJson); - if (cr.error_msg) { - JsonValue response; - response.WithString("body", cr.error_msg).WithInteger("statusCode", 400); - auto const apig_response = response.View().WriteCompact(); - AWS_LOGSTREAM_ERROR(TAG, "Validation failed. " << apig_response); - return aws::lambda_runtime::invocation_response::success(apig_response, "application/json"); - } - - auto result = query(cr, client); - auto const query_response = result.View().WriteCompact(); - AWS_LOGSTREAM_DEBUG(TAG, "query response: " << query_response); - - JsonValue response; - if (result.View().ValueExists("product")) { - response.WithString("body", query_response).WithInteger("statusCode", 200); - } - else { - response.WithString("body", "No data found for this product.").WithInteger("statusCode", 400); - } - - auto const apig_response = response.View().WriteCompact(); - AWS_LOGSTREAM_DEBUG(TAG, "api gateway response: " << apig_response); - - return aws::lambda_runtime::invocation_response::success(apig_response, "application/json"); -} - -std::function()> GetConsoleLoggerFactory() -{ - return [] { - return Aws::MakeShared( - "console_logger", Aws::Utils::Logging::LogLevel::Trace); - }; -} - -int main() -{ - using namespace Aws; - SDKOptions options; - options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace; - options.loggingOptions.logger_create_fn = GetConsoleLoggerFactory(); - InitAPI(options); - { - Aws::Client::ClientConfiguration config; - config.region = Aws::Environment::GetEnv("AWS_REGION"); - config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; - config.disableExpectHeader = true; - - auto credentialsProvider = Aws::MakeShared(TAG); - Aws::DynamoDB::DynamoDBClient client(credentialsProvider, config); - auto handler_fn = [&client](aws::lambda_runtime::invocation_request const& req) { - return my_handler(req, client); - }; - aws::lambda_runtime::run_handler(handler_fn); - } - ShutdownAPI(options); - return 0; -} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/CMakeLists.txt deleted file mode 100644 index b398a4e2d..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -set(CMAKE_CXX_STANDARD 11) -project(encoder LANGUAGES CXX) - -find_package(aws-lambda-runtime) -find_package(AWSSDK COMPONENTS s3) - -add_executable(${PROJECT_NAME} "main.cpp") - -target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES}) - -target_compile_options(${PROJECT_NAME} PRIVATE - "-Wall" - "-Wextra" - "-Wconversion" - "-Wshadow" - "-Wno-sign-conversion") - -target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") - -aws_lambda_package_target(${PROJECT_NAME}) - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/README.md b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/README.md deleted file mode 100644 index 8bc3255a4..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Example using the AWS C++ SDK with Lambda - -We'll build a lambda that downloads an image file from S3 and sends it back in the response as Base64 encoded that can be displayed in a web page for example. -To also show case how this can be done on a Linux distro other than Amazon Linux, you can use the Dockerfile in this directory to create an Alpine Linux environment in which you can run the following instructions. - -That being said, the instructions below should work on any Linux distribution. - -## Build the AWS C++ SDK -Start by building the SDK from source. -```bash -$ mkdir ~/install -$ git clone https://github.com/aws/aws-sdk-cpp.git -$ cd aws-sdk-cpp -$ mkdir build -$ cd build -$ cmake .. -DBUILD_ONLY="s3" \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCUSTOM_MEMORY_MANAGEMENT=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ - -DENABLE_UNITY_BUILD=ON - -$ make -$ make install -``` - -## Build the Runtime -Now let's build the C++ Lambda runtime, so in a separate directory clone this repository and follow these steps: - -```bash -$ git clone https://github.com/awslabs/aws-lambda-cpp-runtime.git -$ cd aws-lambda-cpp-runtime -$ mkdir build -$ cd build -$ cmake .. -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ -$ make -$ make install -``` - -## Build the application -The last step is to build the Lambda function in `main.cpp` and run the packaging command as follows: - -```bash -$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install -$ make -$ make aws-lambda-package-encoder -``` - -You should now have a zip file called `encoder.zip`. Follow the instructions in the main README to upload it and invoke the lambda. diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/main.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/main.cpp deleted file mode 100644 index 45b935bf3..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/examples/s3/main.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace aws::lambda_runtime; - -std::string download_and_encode_file( - Aws::S3::S3Client const& client, - Aws::String const& bucket, - Aws::String const& key, - Aws::String& encoded_output); - -std::string encode(Aws::String const& filename, Aws::String& output); -char const TAG[] = "LAMBDA_ALLOC"; - -static invocation_response my_handler(invocation_request const& req, Aws::S3::S3Client const& client) -{ - using namespace Aws::Utils::Json; - JsonValue json(req.payload); - if (!json.WasParseSuccessful()) { - return invocation_response::failure("Failed to parse input JSON", "InvalidJSON"); - } - - auto v = json.View(); - - if (!v.ValueExists("s3bucket") || !v.ValueExists("s3key") || !v.GetObject("s3bucket").IsString() || - !v.GetObject("s3key").IsString()) { - return invocation_response::failure("Missing input value s3bucket or s3key", "InvalidJSON"); - } - - auto bucket = v.GetString("s3bucket"); - auto key = v.GetString("s3key"); - - AWS_LOGSTREAM_INFO(TAG, "Attempting to download file from s3://" << bucket << "/" << key); - - Aws::String base64_encoded_file; - auto err = download_and_encode_file(client, bucket, key, base64_encoded_file); - if (!err.empty()) { - return invocation_response::failure(err, "DownloadFailure"); - } - - return invocation_response::success(base64_encoded_file, "application/base64"); -} - -std::function()> GetConsoleLoggerFactory() -{ - return [] { - return Aws::MakeShared( - "console_logger", Aws::Utils::Logging::LogLevel::Trace); - }; -} - -int main() -{ - using namespace Aws; - SDKOptions options; - options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace; - options.loggingOptions.logger_create_fn = GetConsoleLoggerFactory(); - InitAPI(options); - { - Client::ClientConfiguration config; - config.region = Aws::Environment::GetEnv("AWS_REGION"); - config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; - - auto credentialsProvider = Aws::MakeShared(TAG); - S3::S3Client client(credentialsProvider, config); - auto handler_fn = [&client](aws::lambda_runtime::invocation_request const& req) { - return my_handler(req, client); - }; - run_handler(handler_fn); - } - ShutdownAPI(options); - return 0; -} - -std::string encode(Aws::IOStream& stream, Aws::String& output) -{ - Aws::Vector bits; - bits.reserve(stream.tellp()); - stream.seekg(0, stream.beg); - - char streamBuffer[1024 * 4]; - while (stream.good()) { - stream.read(streamBuffer, sizeof(streamBuffer)); - auto bytesRead = stream.gcount(); - - if (bytesRead > 0) { - bits.insert(bits.end(), (unsigned char*)streamBuffer, (unsigned char*)streamBuffer + bytesRead); - } - } - Aws::Utils::ByteBuffer bb(bits.data(), bits.size()); - output = Aws::Utils::HashingUtils::Base64Encode(bb); - return {}; -} - -std::string download_and_encode_file( - Aws::S3::S3Client const& client, - Aws::String const& bucket, - Aws::String const& key, - Aws::String& encoded_output) -{ - using namespace Aws; - - S3::Model::GetObjectRequest request; - request.WithBucket(bucket).WithKey(key); - - auto outcome = client.GetObject(request); - if (outcome.IsSuccess()) { - AWS_LOGSTREAM_INFO(TAG, "Download completed!"); - auto& s = outcome.GetResult().GetBody(); - return encode(s, encoded_output); - } - else { - AWS_LOGSTREAM_ERROR(TAG, "Failed with error: " << outcome.GetError()); - return outcome.GetError().GetMessage(); - } -} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/http/response.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/http/response.h deleted file mode 100644 index 9b8cbda1f..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/http/response.h +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include -#include -#include // tolower -#include - -namespace aws { -namespace http { -enum class response_code; -class response { -public: - /** - * lower-case the name but store the value as is - */ - inline void add_header(std::string name, std::string const& value); - inline void append_body(const char* p, size_t sz); - inline bool has_header(char const* header) const; - inline std::string const& get_header(char const* header) const; - inline response_code get_response_code() const { return m_response_code; } - inline void set_response_code(aws::http::response_code c); - inline void set_content_type(char const* ct); - inline std::string const& get_body() const; - -private: - response_code m_response_code; - using key_value_collection = std::vector>; - key_value_collection m_headers; - std::string m_body; - std::string m_content_type; -}; - -enum class response_code { - REQUEST_NOT_MADE = -1, - CONTINUE = 100, - SWITCHING_PROTOCOLS = 101, - PROCESSING = 102, - OK = 200, - CREATED = 201, - ACCEPTED = 202, - NON_AUTHORITATIVE_INFORMATION = 203, - NO_CONTENT = 204, - RESET_CONTENT = 205, - PARTIAL_CONTENT = 206, - MULTI_STATUS = 207, - ALREADY_REPORTED = 208, - IM_USED = 226, - MULTIPLE_CHOICES = 300, - MOVED_PERMANENTLY = 301, - FOUND = 302, - SEE_OTHER = 303, - NOT_MODIFIED = 304, - USE_PROXY = 305, - SWITCH_PROXY = 306, - TEMPORARY_REDIRECT = 307, - PERMANENT_REDIRECT = 308, - BAD_REQUEST = 400, - UNAUTHORIZED = 401, - PAYMENT_REQUIRED = 402, - FORBIDDEN = 403, - NOT_FOUND = 404, - METHOD_NOT_ALLOWED = 405, - NOT_ACCEPTABLE = 406, - PROXY_AUTHENTICATION_REQUIRED = 407, - REQUEST_TIMEOUT = 408, - CONFLICT = 409, - GONE = 410, - LENGTH_REQUIRED = 411, - PRECONDITION_FAILED = 412, - REQUEST_ENTITY_TOO_LARGE = 413, - REQUEST_URI_TOO_LONG = 414, - UNSUPPORTED_MEDIA_TYPE = 415, - REQUESTED_RANGE_NOT_SATISFIABLE = 416, - EXPECTATION_FAILED = 417, - IM_A_TEAPOT = 418, - AUTHENTICATION_TIMEOUT = 419, - METHOD_FAILURE = 420, - UNPROC_ENTITY = 422, - LOCKED = 423, - FAILED_DEPENDENCY = 424, - UPGRADE_REQUIRED = 426, - PRECONDITION_REQUIRED = 427, - TOO_MANY_REQUESTS = 429, - REQUEST_HEADER_FIELDS_TOO_LARGE = 431, - LOGIN_TIMEOUT = 440, - NO_RESPONSE = 444, - RETRY_WITH = 449, - BLOCKED = 450, - REDIRECT = 451, - REQUEST_HEADER_TOO_LARGE = 494, - CERT_ERROR = 495, - NO_CERT = 496, - HTTP_TO_HTTPS = 497, - CLIENT_CLOSED_TO_REQUEST = 499, - INTERNAL_SERVER_ERROR = 500, - NOT_IMPLEMENTED = 501, - BAD_GATEWAY = 502, - SERVICE_UNAVAILABLE = 503, - GATEWAY_TIMEOUT = 504, - HTTP_VERSION_NOT_SUPPORTED = 505, - VARIANT_ALSO_NEGOTIATES = 506, - INSUFFICIENT_STORAGE = 506, - LOOP_DETECTED = 508, - BANDWIDTH_LIMIT_EXCEEDED = 509, - NOT_EXTENDED = 510, - NETWORK_AUTHENTICATION_REQUIRED = 511, - NETWORK_READ_TIMEOUT = 598, - NETWORK_CONNECT_TIMEOUT = 599 -}; - -inline void response::set_response_code(http::response_code c) -{ - m_response_code = c; -} - -inline void response::set_content_type(char const* ct) -{ - m_content_type = ct; -} - -inline std::string const& response::get_body() const -{ - return m_body; -} -inline void response::add_header(std::string name, std::string const& value) -{ - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - m_headers.emplace_back(name, value); -} - -inline void response::append_body(const char* p, size_t sz) -{ - // simple and generates significantly less code than std::stringstream - constexpr size_t min_capacity = 512; - if (m_body.capacity() < min_capacity) { - m_body.reserve(min_capacity); - } - - m_body.append(p, sz); -} - -inline bool response::has_header(char const* header) const -{ - return std::any_of(m_headers.begin(), m_headers.end(), [header](std::pair const& p) { - return p.first == header; - }); -} - -inline std::string const& response::get_header(char const* header) const -{ - auto it = std::find_if(m_headers.begin(), m_headers.end(), [header](std::pair const& p) { - return p.first == header; - }); - assert(it != m_headers.end()); - return it->second; -} - -} // namespace http -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/outcome.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/outcome.h deleted file mode 100644 index b5d0b8b0a..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/outcome.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include - -namespace aws { -namespace lambda_runtime { - -template -class outcome { -public: - outcome(TResult const& s) : m_s(s), m_success(true) {} - outcome(TResult&& s) : m_s(std::move(s)), m_success(true) {} - - outcome(TFailure const& f) : m_f(f), m_success(false) {} - outcome(TFailure&& f) : m_f(std::move(f)), m_success(false) {} - - outcome(outcome const& other) : m_success(other.m_success) - { - if (m_success) { - new (&m_s) TResult(other.m_s); - } - else { - new (&m_f) TFailure(other.m_f); - } - } - - outcome(outcome&& other) noexcept : m_success(other.m_success) - { - if (m_success) { - new (&m_s) TResult(std::move(other.m_s)); - } - else { - new (&m_f) TFailure(std::move(other.m_f)); - } - } - - ~outcome() - { - if (m_success) { - m_s.~TResult(); - } - else { - m_f.~TFailure(); - } - } - - TResult const& get_result() const& - { - assert(m_success); - return m_s; - } - - TResult&& get_result() && - { - assert(m_success); - return std::move(m_s); - } - - TFailure const& get_failure() const& - { - assert(!m_success); - return m_f; - } - - TFailure&& get_failure() && - { - assert(!m_success); - return std::move(m_f); - } - - bool is_success() const { return m_success; } - -private: - union { - TResult m_s; - TFailure m_f; - }; - bool m_success; -}; -} // namespace lambda_runtime -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h deleted file mode 100644 index 96f90eaa2..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/runtime.h +++ /dev/null @@ -1,197 +0,0 @@ -#pragma once -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include -#include -#include -#include -#include "aws/lambda-runtime/outcome.h" -#include "aws/http/response.h" - -namespace aws { -namespace lambda_runtime { - -struct invocation_request { - /** - * The user's payload represented as a UTF-8 string. - */ - std::string payload; - - /** - * An identifier unique to the current invocation. - */ - std::string request_id; - - /** - * X-Ray tracing ID of the current invocation. - */ - std::string xray_trace_id; - - /** - * Information about the client application and device when invoked through the AWS Mobile SDK. - */ - std::string client_context; - - /** - * Information about the Amazon Cognito identity provider when invoked through the AWS Mobile SDK. - */ - std::string cognito_identity; - - /** - * The ARN requested. This can be different in each invoke that executes the same version. - */ - std::string function_arn; - - /** - * Function execution deadline counted in milliseconds since the Unix epoch. - */ - std::chrono::time_point deadline; - - /** - * Tenant ID of the current invocation. - */ - std::string tenant_id; - - /** - * Invocation ID for cross-wiring protection. - */ - std::string invocation_id; - - /** - * The number of milliseconds left before lambda terminates the current execution. - */ - inline std::chrono::milliseconds get_time_remaining() const; -}; - -class invocation_response { -private: - /** - * The output of the function which is sent to the lambda caller. - */ - std::string m_payload; - - /** - * The MIME type of the payload. - * This is always set to 'application/json' in unsuccessful invocations. - */ - std::string m_content_type; - - /** - * Flag to distinguish if the contents are for successful or unsuccessful invocations. - */ - bool m_success; - - /** - * Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated - * invocation_response - */ - invocation_response() = default; - -public: - // Create a success or failure response. Typically, you should use the static functions invocation_response::success - // and invocation_response::failure, however, invocation_response::failure doesn't allow for arbitrary payloads. - // To support clients that need to control the entire error response body (e.g. adding a stack trace), this - // constructor should be used instead. - // Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same. - invocation_response(std::string const& payload, std::string const& content_type, bool success) - : m_payload(payload), m_content_type(content_type), m_success(success) - { - } - - /** - * Create a successful invocation response with the given payload and content-type. - */ - static invocation_response success(std::string const& payload, std::string const& content_type); - - /** - * Create a failure response with the given error message and error type. - * The content-type is always set to application/json in this case. - */ - static invocation_response failure(std::string const& error_message, std::string const& error_type); - - /** - * Get the MIME type of the payload. - */ - std::string const& get_content_type() const { return m_content_type; } - - /** - * Get the payload string. The string is assumed to be UTF-8 encoded. - */ - std::string const& get_payload() const { return m_payload; } - - /** - * Returns true if the payload and content-type are set. Returns false if the error message and error types are set. - */ - bool is_success() const { return m_success; } -}; - -struct no_result { -}; - -class runtime { -public: - using next_outcome = aws::lambda_runtime::outcome; - using post_outcome = aws::lambda_runtime::outcome; - - runtime(std::string const& endpoint, std::string const& user_agent); - runtime(std::string const& endpoint); - ~runtime(); - - /** - * Ask lambda for an invocation. - */ - next_outcome get_next(); - - /** - * Tells lambda that the function has succeeded. - */ - post_outcome post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id); - - post_outcome post_success(std::string const& request_id, invocation_response const& handler_response); - - /** - * Tells lambda that the function has failed. - */ - post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id); - - post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); - -private: - void set_curl_next_options(); - void set_curl_post_result_options(); - post_outcome do_post( - std::string const& url, - std::string const& request_id, - invocation_response const& handler_response, - std::string const& invocation_id); - -private: - std::string const m_user_agent_header; - std::array const m_endpoints; -}; - -inline std::chrono::milliseconds invocation_request::get_time_remaining() const -{ - using namespace std::chrono; - return duration_cast(deadline - system_clock::now()); -} - -// Entry method -void run_handler(std::function const& handler); - -} // namespace lambda_runtime -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/version.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/version.h deleted file mode 100644 index eafcbde76..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/lambda-runtime/version.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -namespace aws { -namespace lambda_runtime { - -/** - * Returns the major component of the library version. - */ -unsigned get_version_major(); - -/** - * Returns the minor component of the library version. - */ -unsigned get_version_minor(); - -/** - * Returns the patch component of the library version. - */ -unsigned get_version_patch(); - -/** - * Returns the semantic version of the library in the form Major.Minor.Patch - */ -char const* get_version(); - -} // namespace lambda_runtime -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/logging/logging.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/logging/logging.h deleted file mode 100644 index 0b5d0ef96..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/include/aws/logging/logging.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include - -namespace aws { -namespace logging { - -enum class verbosity { - error, - info, - debug, -}; - -void log(verbosity v, char const* tag, char const* msg, va_list args); - -[[gnu::format(printf, 2, 3)]] inline void log_error(char const* tag, char const* msg, ...) -{ - va_list args; - va_start(args, msg); - log(verbosity::error, tag, msg, args); - va_end(args); - (void)tag; - (void)msg; -} - -[[gnu::format(printf, 2, 3)]] inline void log_info(char const* tag, char const* msg, ...) -{ -#if AWS_LAMBDA_LOG >= 1 - va_list args; - va_start(args, msg); - log(verbosity::info, tag, msg, args); - va_end(args); -#else - (void)tag; - (void)msg; -#endif -} - -[[gnu::format(printf, 2, 3)]] inline void log_debug(char const* tag, char const* msg, ...) -{ -#if AWS_LAMBDA_LOG >= 2 - va_list args; - va_start(args, msg); - log(verbosity::debug, tag, msg, args); - va_end(args); -#else - (void)tag; - (void)msg; -#endif -} - -} // namespace logging -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/packaging/packager b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/packaging/packager deleted file mode 100755 index d33389166..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/packaging/packager +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash -# Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). -# You may not use this file except in compliance with the License. -# A copy of the License is located at -# -# http://aws.amazon.com/apache2.0 -# -# or in the "license" file accompanying this file. This file is distributed -# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language governing -# permissions and limitations under the License. - -set -euo pipefail - -print_help() { - echo -e "Usage: packager [OPTIONS] \n" - echo -e "OPTIONS\n" - echo -e "\t-d,--default-libc\t Use the target host libc libraries. This will not package the C library files.\n" -} - -if [ $# -lt 1 ]; then - echo -e "Error: missing arguments\n" - print_help - exit 1 -fi - -POSITIONAL=() -INCLUDE_LIBC=true -while [[ $# -gt 0 ]] -do - key="$1" - case $key in - -d|--default-libc) - INCLUDE_LIBC=false - shift # past argument - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; - esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -PKG_BIN_PATH=$1 - -if [ ! -f "$PKG_BIN_PATH" ]; then - echo "$PKG_BIN_PATH" - No such file.; - exit 1; -fi - -if ! type zip > /dev/null 2>&1; then - echo "zip utility is not found. Please install it and re-run this script" - exit 1 -fi -function package_libc_via_pacman { - if grep --extended-regexp "Arch Linux|Manjaro Linux" < /etc/os-release > /dev/null 2>&1; then - if type pacman > /dev/null 2>&1; then - pacman --query --list --quiet glibc | sed -E '/\.so$|\.so\.[0-9]+$/!d' - fi - fi -} - -function package_libc_via_dpkg() { - if type dpkg-query > /dev/null 2>&1; then - if [[ $(dpkg-query --listfiles libc6 | wc -l) -gt 0 ]]; then - dpkg-query --listfiles libc6 | sed -E '/\.so$|\.so\.[0-9]+$/!d' - fi - fi -} - -function package_libc_via_rpm() { - if type rpm > /dev/null 2>&1; then - if [[ $(rpm --query --list glibc.x86_64 | wc -l) -gt 1 ]]; then - rpm --query --list glibc.x86_64 | sed -E '/\.so$|\.so\.[0-9]+$/!d' - fi - fi -} - -# hasElement expects an element and an array parameter -# it's equivalent to array.contains(element) -# e.g. hasElement "needle" ${haystack[@]} -function hasElement() { - local el key=$1 - shift - for el in "$@" - do - [[ "$el" == "$key" ]] && return 0 - done - return 1 -} - -PKG_BIN_FILENAME=$(basename "$PKG_BIN_PATH") -PKG_DIR=tmp -PKG_LD="" - -list=$(ldd "$PKG_BIN_PATH" | awk '{print $(NF-1)}') -libc_libs=() -libc_libs+=($(package_libc_via_dpkg)) -libc_libs+=($(package_libc_via_rpm)) -libc_libs+=($(package_libc_via_pacman)) - -mkdir -p "$PKG_DIR/bin" "$PKG_DIR/lib" - -for i in $list -do - if [[ ! -f $i ]]; then # ignore linux-vdso.so.1 - continue - fi - - # Do not copy libc files which are directly linked unless it's the dynamic loader - if hasElement "$i" "${libc_libs[@]}"; then - filename=$(basename "$i") - if [[ -z "${filename##ld-*}" ]]; then - PKG_LD=$filename # Use this file as the loader - cp "$i" "$PKG_DIR/lib" - fi - continue - fi - - cp "$i" $PKG_DIR/lib -done - -if [[ $INCLUDE_LIBC == true ]]; then - for i in "${libc_libs[@]}" - do - filename=$(basename "$i") - if [[ -z "${filename##ld-*}" ]]; then - # if the loader is empty, then the binary is probably linked to a symlink of the loader. The symlink will - # not show up when quering the package manager for libc files. So, in this case, we want to copy the loader - if [[ -z "$PKG_LD" ]]; then - PKG_LD=$filename - cp "$i" "$PKG_DIR/lib" # we want to follow the symlink (default behavior) - fi - continue # We don't want the dynamic loader's symlink because its target is an absolute path (/lib/ld-*). - fi - cp --no-dereference "$i" "$PKG_DIR/lib" - done -fi - -if [[ -z "$PKG_LD" ]]; then - echo "Failed to identify, locate or package the loader. Please file an issue on Github!" 1>&2 - exit 1 -fi - -bootstrap_script=$(cat < "$PKG_DIR/bootstrap" -else - echo -e "$bootstrap_script_no_libc" > "$PKG_DIR/bootstrap" -fi -chmod +x "$PKG_DIR/bootstrap" -# some shenanigans to create the right layout in the zip file without extraneous directories -pushd "$PKG_DIR" > /dev/null -zip --symlinks --recurse-paths "$PKG_BIN_FILENAME".zip -- * -ORIGIN_DIR=$(dirs -l +1) -mv "$PKG_BIN_FILENAME".zip "$ORIGIN_DIR" -popd > /dev/null -rm -r "$PKG_DIR" -echo Created "$ORIGIN_DIR/$PKG_BIN_FILENAME".zip - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.cpp deleted file mode 100644 index cc64abdbc..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Pick your poison. -// -// On GNU/Linux, you have few choices to get the most out of your stack trace. -// -// By default you get: -// - object filename -// - function name -// -// In order to add: -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) - -// Install one of the following library then uncomment one of the macro (or -// better, add the detection of the lib and the macro definition in your build -// system) - -// - apt-get install libdw-dev ... -// - g++/clang++ -ldw ... -// #define BACKWARD_HAS_DW 1 - -// - apt-get install binutils-dev ... -// - g++/clang++ -lbfd ... -// #define BACKWARD_HAS_BFD 1 - -#include "backward.h" - -namespace backward { - -backward::SignalHandling sh; - -} // namespace backward diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.h deleted file mode 100644 index e9e56c798..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/backward.h +++ /dev/null @@ -1,4291 +0,0 @@ -// clang-format off -/* - * backward.hpp - * Copyright 2013 Google Inc. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef H_6B9572DA_A64B_49E6_B234_051480991C89 -#define H_6B9572DA_A64B_49E6_B234_051480991C89 - -#ifndef __cplusplus -# error "It's not going to compile without a C++ compiler..." -#endif - -#if defined(BACKWARD_CXX11) -#elif defined(BACKWARD_CXX98) -#else -# if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800) -# define BACKWARD_CXX11 -# define BACKWARD_ATLEAST_CXX11 -# define BACKWARD_ATLEAST_CXX98 -# else -# define BACKWARD_CXX98 -# define BACKWARD_ATLEAST_CXX98 -# endif -#endif - -// You can define one of the following (or leave it to the auto-detection): -// -// #define BACKWARD_SYSTEM_LINUX -// - specialization for linux -// -// #define BACKWARD_SYSTEM_DARWIN -// - specialization for Mac OS X 10.5 and later. -// -// #define BACKWARD_SYSTEM_UNKNOWN -// - placebo implementation, does nothing. -// -#if defined(BACKWARD_SYSTEM_LINUX) -#elif defined(BACKWARD_SYSTEM_DARWIN) -#elif defined(BACKWARD_SYSTEM_UNKNOWN) -#elif defined(BACKWARD_SYSTEM_WINDOWS) -#else -# if defined(__linux) || defined(__linux__) -# define BACKWARD_SYSTEM_LINUX -# elif defined(__APPLE__) -# define BACKWARD_SYSTEM_DARWIN -# elif defined(_WIN32) -# define BACKWARD_SYSTEM_WINDOWS -# else -# define BACKWARD_SYSTEM_UNKNOWN -# endif -#endif - -#define NOINLINE __attribute__((noinline)) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(BACKWARD_SYSTEM_LINUX) - -// On linux, backtrace can back-trace or "walk" the stack using the following -// libraries: -// -// #define BACKWARD_HAS_UNWIND 1 -// - unwind comes from libgcc, but I saw an equivalent inside clang itself. -// - with unwind, the stacktrace is as accurate as it can possibly be, since -// this is used by the C++ runtine in gcc/clang for stack unwinding on -// exception. -// - normally libgcc is already linked to your program by default. -// -// #define BACKWARD_HAS_BACKTRACE == 1 -// - backtrace seems to be a little bit more portable than libunwind, but on -// linux, it uses unwind anyway, but abstract away a tiny information that is -// sadly really important in order to get perfectly accurate stack traces. -// - backtrace is part of the (e)glib library. -// -// The default is: -// #define BACKWARD_HAS_UNWIND == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -# if BACKWARD_HAS_UNWIND == 1 -# elif BACKWARD_HAS_BACKTRACE == 1 -# else -# undef BACKWARD_HAS_UNWIND -# define BACKWARD_HAS_UNWIND 1 -# undef BACKWARD_HAS_BACKTRACE -# define BACKWARD_HAS_BACKTRACE 0 -# endif - -// On linux, backward can extract detailed information about a stack trace -// using one of the following libraries: -// -// #define BACKWARD_HAS_DW 1 -// - libdw gives you the most juicy details out of your stack traces: -// - object filename -// - function name -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) -// - You need to link with the lib "dw": -// - apt-get install libdw-dev -// - g++/clang++ -ldw ... -// -// #define BACKWARD_HAS_BFD 1 -// - With libbfd, you get a fair amount of details: -// - object filename -// - function name -// - source filename -// - line numbers -// - source code snippet (assuming the file is accessible) -// - You need to link with the lib "bfd": -// - apt-get install binutils-dev -// - g++/clang++ -lbfd ... -// -// #define BACKWARD_HAS_DWARF 1 -// - libdwarf gives you the most juicy details out of your stack traces: -// - object filename -// - function name -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) -// - You need to link with the lib "dwarf": -// - apt-get install libdwarf-dev -// - g++/clang++ -ldwarf ... -// -// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -// - backtrace provides minimal details for a stack trace: -// - object filename -// - function name -// - backtrace is part of the (e)glib library. -// -// The default is: -// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -# if BACKWARD_HAS_DW == 1 -# elif BACKWARD_HAS_BFD == 1 -# elif BACKWARD_HAS_DWARF == 1 -# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -# else -# undef BACKWARD_HAS_DW -# define BACKWARD_HAS_DW 0 -# undef BACKWARD_HAS_BFD -# define BACKWARD_HAS_BFD 0 -# undef BACKWARD_HAS_DWARF -# define BACKWARD_HAS_DWARF 0 -# undef BACKWARD_HAS_BACKTRACE_SYMBOL -# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -# endif - -# include -# include -# ifdef __ANDROID__ -// Old Android API levels define _Unwind_Ptr in both link.h and -// unwind.h Rename the one in link.h as we are not going to be using -// it -# define _Unwind_Ptr _Unwind_Ptr_Custom -# include -# undef _Unwind_Ptr -# else -# include -# endif -# include -# include -# include -# include - -# if BACKWARD_HAS_BFD == 1 -// NOTE: defining PACKAGE{,_VERSION} is required before including -// bfd.h on some platforms, see also: -// https://sourceware.org/bugzilla/show_bug.cgi?id=14243 -# ifndef PACKAGE -# define PACKAGE -# endif -# ifndef PACKAGE_VERSION -# define PACKAGE_VERSION -# endif -# include -# ifndef _GNU_SOURCE -# define _GNU_SOURCE -# include -# undef _GNU_SOURCE -# else -# include -# endif -# endif - -# if BACKWARD_HAS_DW == 1 -# include -# include -# include -# endif - -# if BACKWARD_HAS_DWARF == 1 -# include -# include -# include -# include -# include -# ifndef _GNU_SOURCE -# define _GNU_SOURCE -# include -# undef _GNU_SOURCE -# else -# include -# endif -# endif - -# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) -// then we shall rely on backtrace -# include -# endif - -#endif // defined(BACKWARD_SYSTEM_LINUX) - -#if defined(BACKWARD_SYSTEM_DARWIN) -// On Darwin, backtrace can back-trace or "walk" the stack using the following -// libraries: -// -// #define BACKWARD_HAS_UNWIND 1 -// - unwind comes from libgcc, but I saw an equivalent inside clang itself. -// - with unwind, the stacktrace is as accurate as it can possibly be, since -// this is used by the C++ runtine in gcc/clang for stack unwinding on -// exception. -// - normally libgcc is already linked to your program by default. -// -// #define BACKWARD_HAS_BACKTRACE == 1 -// - backtrace is available by default, though it does not produce as much -// information as another library might. -// -// The default is: -// #define BACKWARD_HAS_UNWIND == 1 -// -// Note that only one of the define should be set to 1 at a time. -// -# if BACKWARD_HAS_UNWIND == 1 -# elif BACKWARD_HAS_BACKTRACE == 1 -# else -# undef BACKWARD_HAS_UNWIND -# define BACKWARD_HAS_UNWIND 1 -# undef BACKWARD_HAS_BACKTRACE -# define BACKWARD_HAS_BACKTRACE 0 -# endif - -// On Darwin, backward can extract detailed information about a stack trace -// using one of the following libraries: -// -// #define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -// - backtrace provides minimal details for a stack trace: -// - object filename -// - function name -// -// The default is: -// #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -// -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -# else -# undef BACKWARD_HAS_BACKTRACE_SYMBOL -# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -# endif - -# include -# include -# include -# include -# include -# include - -# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) -# include -# endif -#endif // defined(BACKWARD_SYSTEM_DARWIN) - -#if defined(BACKWARD_SYSTEM_WINDOWS) - -# include -# include -# include - -# include -typedef SSIZE_T ssize_t; - -# define NOMINMAX -# include -# include - -# include -# include - -# ifndef __clang__ -# undef NOINLINE -# define NOINLINE __declspec(noinline) -# endif - -# pragma comment(lib, "psapi.lib") -# pragma comment(lib, "dbghelp.lib") - -// Comment / packing is from stackoverflow: -// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 -// Some versions of imagehlp.dll lack the proper packing directives themselves -// so we need to do it. -# pragma pack(push, before_imagehlp, 8) -# include -# pragma pack(pop, before_imagehlp) - -// TODO maybe these should be undefined somewhere else? -# undef BACKWARD_HAS_UNWIND -# undef BACKWARD_HAS_BACKTRACE -# if BACKWARD_HAS_PDB_SYMBOL == 1 -# else -# undef BACKWARD_HAS_PDB_SYMBOL -# define BACKWARD_HAS_PDB_SYMBOL 1 -# endif - -#endif - -#if BACKWARD_HAS_UNWIND == 1 - -# include -// while gcc's unwind.h defines something like that: -// extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *); -// extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *); -// -// clang's unwind.h defines something like this: -// uintptr_t _Unwind_GetIP(struct _Unwind_Context* __context); -// -// Even if the _Unwind_GetIPInfo can be linked to, it is not declared, worse we -// cannot just redeclare it because clang's unwind.h doesn't define _Unwind_Ptr -// anyway. -// -// Luckily we can play on the fact that the guard macros have a different name: -# ifdef __CLANG_UNWIND_H -// In fact, this function still comes from libgcc (on my different linux boxes, -// clang links against libgcc). -# include -extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context*, int*); -# endif - -#endif // BACKWARD_HAS_UNWIND == 1 - -#ifdef BACKWARD_ATLEAST_CXX11 -# include -# include // for std::swap -namespace backward { -namespace details { -template -struct hashtable { - typedef std::unordered_map type; -}; -using std::move; -} // namespace details -} // namespace backward -#else // NOT BACKWARD_ATLEAST_CXX11 -# define nullptr NULL -# define override -# include -namespace backward { -namespace details { -template -struct hashtable { - typedef std::map type; -}; -template -const T& move(const T& v) -{ - return v; -} -template -T& move(T& v) -{ - return v; -} -} // namespace details -} // namespace backward -#endif // BACKWARD_ATLEAST_CXX11 - -namespace backward { -namespace details { -#if defined(BACKWARD_SYSTEM_WINDOWS) -const char kBackwardPathDelimiter[] = ";"; -#else -const char kBackwardPathDelimiter[] = ":"; -#endif -} // namespace details -} // namespace backward - -namespace backward { - -namespace system_tag { -struct linux_tag; // seems that I cannot call that "linux" because the name -// is already defined... so I am adding _tag everywhere. -struct darwin_tag; -struct windows_tag; -struct unknown_tag; - -#if defined(BACKWARD_SYSTEM_LINUX) -typedef linux_tag current_tag; -#elif defined(BACKWARD_SYSTEM_DARWIN) -typedef darwin_tag current_tag; -#elif defined(BACKWARD_SYSTEM_WINDOWS) -typedef windows_tag current_tag; -#elif defined(BACKWARD_SYSTEM_UNKNOWN) -typedef unknown_tag current_tag; -#else -# error "May I please get my system defines?" -#endif -} // namespace system_tag - -namespace trace_resolver_tag { -#if defined(BACKWARD_SYSTEM_LINUX) -struct libdw; -struct libbfd; -struct libdwarf; -struct backtrace_symbol; - -# if BACKWARD_HAS_DW == 1 -typedef libdw current; -# elif BACKWARD_HAS_BFD == 1 -typedef libbfd current; -# elif BACKWARD_HAS_DWARF == 1 -typedef libdwarf current; -# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -typedef backtrace_symbol current; -# else -# error "You shall not pass, until you know what you want." -# endif -#elif defined(BACKWARD_SYSTEM_DARWIN) -struct backtrace_symbol; - -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -typedef backtrace_symbol current; -# else -# error "You shall not pass, until you know what you want." -# endif -#elif defined(BACKWARD_SYSTEM_WINDOWS) -struct pdb_symbol; -# if BACKWARD_HAS_PDB_SYMBOL == 1 -typedef pdb_symbol current; -# else -# error "You shall not pass, until you know what you want." -# endif -#endif -} // namespace trace_resolver_tag - -namespace details { - -template -struct rm_ptr { - typedef T type; -}; - -template -struct rm_ptr { - typedef T type; -}; - -template -struct rm_ptr { - typedef const T type; -}; - -template -struct deleter { - template - void operator()(U& ptr) const - { - (*F)(ptr); - } -}; - -template -struct default_delete { - void operator()(T& ptr) const { delete ptr; } -}; - -template > -class handle { - struct dummy; - T _val; - bool _empty; - -#ifdef BACKWARD_ATLEAST_CXX11 - handle(const handle&) = delete; - handle& operator=(const handle&) = delete; -#endif - -public: - ~handle() - { - if (!_empty) { - Deleter()(_val); - } - } - - explicit handle() : _val(), _empty(true) {} - explicit handle(T val) : _val(val), _empty(false) - { - if (!_val) - _empty = true; - } - -#ifdef BACKWARD_ATLEAST_CXX11 - handle(handle&& from) : _empty(true) { swap(from); } - handle& operator=(handle&& from) - { - swap(from); - return *this; - } -#else - explicit handle(const handle& from) : _empty(true) - { - // some sort of poor man's move semantic. - swap(const_cast(from)); - } - handle& operator=(const handle& from) - { - // some sort of poor man's move semantic. - swap(const_cast(from)); - return *this; - } -#endif - - void reset(T new_val) - { - handle tmp(new_val); - swap(tmp); - } - - void update(T new_val) - { - _val = new_val; - _empty = static_cast(new_val); - } - - operator const dummy*() const - { - if (_empty) { - return nullptr; - } - return reinterpret_cast(_val); - } - T get() { return _val; } - T release() - { - _empty = true; - return _val; - } - void swap(handle& b) - { - using std::swap; - swap(b._val, _val); // can throw, we are safe here. - swap(b._empty, _empty); // should not throw: if you cannot swap two - // bools without throwing... It's a lost cause anyway! - } - - T& operator->() { return _val; } - const T& operator->() const { return _val; } - - typedef typename rm_ptr::type& ref_t; - typedef const typename rm_ptr::type& const_ref_t; - ref_t operator*() { return *_val; } - const_ref_t operator*() const { return *_val; } - ref_t operator[](size_t idx) { return _val[idx]; } - - // Watch out, we've got a badass over here - T* operator&() - { - _empty = false; - return &_val; - } -}; - -// Default demangler implementation (do nothing). -template -struct demangler_impl { - static std::string demangle(const char* funcname) { return funcname; } -}; - -#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) - -template <> -struct demangler_impl { - demangler_impl() : _demangle_buffer_length(0) {} - - std::string demangle(const char* funcname) - { - using namespace details; - char* result = abi::__cxa_demangle(funcname, _demangle_buffer.get(), &_demangle_buffer_length, nullptr); - if (result) { - _demangle_buffer.update(result); - return result; - } - return funcname; - } - -private: - details::handle _demangle_buffer; - size_t _demangle_buffer_length; -}; - -#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN - -struct demangler : public demangler_impl { -}; - -// Split a string on the platform's PATH delimiter. Example: if delimiter -// is ":" then: -// "" --> [] -// ":" --> ["",""] -// "::" --> ["","",""] -// "/a/b/c" --> ["/a/b/c"] -// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"] -// etc. -inline std::vector split_source_prefixes(const std::string& s) -{ - std::vector out; - size_t last = 0; - size_t next = 0; - size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1; - while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) { - out.push_back(s.substr(last, next - last)); - last = next + delimiter_size; - } - if (last <= s.length()) { - out.push_back(s.substr(last)); - } - return out; -} - -} // namespace details - -/*************** A TRACE ***************/ - -struct Trace { - void* addr; - size_t idx; - - Trace() : addr(nullptr), idx(0) {} - - explicit Trace(void* _addr, size_t _idx) : addr(_addr), idx(_idx) {} -}; - -struct ResolvedTrace : public Trace { - - struct SourceLoc { - std::string function; - std::string filename; - unsigned line; - unsigned col; - - SourceLoc() : line(0), col(0) {} - - bool operator==(const SourceLoc& b) const - { - return function == b.function && filename == b.filename && line == b.line && col == b.col; - } - - bool operator!=(const SourceLoc& b) const { return !(*this == b); } - }; - - // In which binary object this trace is located. - std::string object_filename; - - // The function in the object that contain the trace. This is not the same - // as source.function which can be an function inlined in object_function. - std::string object_function; - - // The source location of this trace. It is possible for filename to be - // empty and for line/col to be invalid (value 0) if this information - // couldn't be deduced, for example if there is no debug information in the - // binary object. - SourceLoc source; - - // An optionals list of "inliners". All the successive sources location - // from where the source location of the trace (the attribute right above) - // is inlined. It is especially useful when you compiled with optimization. - typedef std::vector source_locs_t; - source_locs_t inliners; - - ResolvedTrace() : Trace() {} - ResolvedTrace(const Trace& mini_trace) : Trace(mini_trace) {} -}; - -/*************** STACK TRACE ***************/ - -// default implemention. -template -class StackTraceImpl { -public: - size_t size() const { return 0; } - Trace operator[](size_t) const { return Trace(); } - size_t load_here(size_t = 0) { return 0; } - size_t load_from(void*, size_t = 0) { return 0; } - size_t thread_id() const { return 0; } - void skip_n_firsts(size_t) {} -}; - -class StackTraceImplBase { -public: - StackTraceImplBase() : _thread_id(0), _skip(0) {} - - size_t thread_id() const { return _thread_id; } - - void skip_n_firsts(size_t n) { _skip = n; } - -protected: - void load_thread_info() - { -#ifdef BACKWARD_SYSTEM_LINUX -# ifndef __ANDROID__ - _thread_id = static_cast(syscall(SYS_gettid)); -# else - _thread_id = static_cast(gettid()); -# endif - if (_thread_id == static_cast(getpid())) { - // If the thread is the main one, let's hide that. - // I like to keep little secret sometimes. - _thread_id = 0; - } -#elif defined(BACKWARD_SYSTEM_DARWIN) - _thread_id = reinterpret_cast(pthread_self()); - if (pthread_main_np() == 1) { - // If the thread is the main one, let's hide that. - _thread_id = 0; - } -#endif - } - - size_t skip_n_firsts() const { return _skip; } - -private: - size_t _thread_id; - size_t _skip; -}; - -class StackTraceImplHolder : public StackTraceImplBase { -public: - size_t size() const { return _stacktrace.size() ? _stacktrace.size() - skip_n_firsts() : 0; } - Trace operator[](size_t idx) const - { - if (idx >= size()) { - return Trace(); - } - return Trace(_stacktrace[idx + skip_n_firsts()], idx); - } - void* const* begin() const - { - if (size()) { - return &_stacktrace[skip_n_firsts()]; - } - return nullptr; - } - -protected: - std::vector _stacktrace; -}; - -#if BACKWARD_HAS_UNWIND == 1 - -namespace details { - -template -class Unwinder { -public: - size_t operator()(F& f, size_t depth) - { - _f = &f; - _index = -1; - _depth = depth; - _Unwind_Backtrace(&this->backtrace_trampoline, this); - return static_cast(_index); - } - -private: - F* _f; - ssize_t _index; - size_t _depth; - - static _Unwind_Reason_Code backtrace_trampoline(_Unwind_Context* ctx, void* self) - { - return (static_cast(self))->backtrace(ctx); - } - - _Unwind_Reason_Code backtrace(_Unwind_Context* ctx) - { - if (_index >= 0 && static_cast(_index) >= _depth) - return _URC_END_OF_STACK; - - int ip_before_instruction = 0; - uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction); - - if (!ip_before_instruction) { - // calculating 0-1 for unsigned, looks like a possible bug to sanitiziers, - // so let's do it explicitly: - if (ip == 0) { - ip = std::numeric_limits::max(); // set it to 0xffff... (as - // from casting 0-1) - } - else { - ip -= 1; // else just normally decrement it (no overflow/underflow will - // happen) - } - } - - if (_index >= 0) { // ignore first frame. - (*_f)(static_cast(_index), reinterpret_cast(ip)); - } - _index += 1; - return _URC_NO_REASON; - } -}; - -template -size_t unwind(F f, size_t depth) -{ - Unwinder unwinder; - return unwinder(f, depth); -} - -} // namespace details - -template <> -class StackTraceImpl : public StackTraceImplHolder { -public: - NOINLINE - size_t load_here(size_t depth = 32) - { - load_thread_info(); - if (depth == 0) { - return 0; - } - _stacktrace.resize(depth); - size_t trace_cnt = details::unwind(callback(*this), depth); - _stacktrace.resize(trace_cnt); - skip_n_firsts(0); - return size(); - } - size_t load_from(void* addr, size_t depth = 32) - { - load_here(depth + 8); - - for (size_t i = 0; i < _stacktrace.size(); ++i) { - if (_stacktrace[i] == addr) { - skip_n_firsts(i); - break; - } - } - - _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); - return size(); - } - -private: - struct callback { - StackTraceImpl& self; - callback(StackTraceImpl& _self) : self(_self) {} - - void operator()(size_t idx, void* addr) { self._stacktrace[idx] = addr; } - }; -}; - -#elif defined(BACKWARD_HAS_BACKTRACE) - -template <> -class StackTraceImpl : public StackTraceImplHolder { -public: - NOINLINE - size_t load_here(size_t depth = 32) - { - load_thread_info(); - if (depth == 0) { - return 0; - } - _stacktrace.resize(depth + 1); - size_t trace_cnt = backtrace(&_stacktrace[0], _stacktrace.size()); - _stacktrace.resize(trace_cnt); - skip_n_firsts(1); - return size(); - } - - size_t load_from(void* addr, size_t depth = 32) - { - load_here(depth + 8); - - for (size_t i = 0; i < _stacktrace.size(); ++i) { - if (_stacktrace[i] == addr) { - skip_n_firsts(i); - _stacktrace[i] = (void*)((uintptr_t)_stacktrace[i] + 1); - break; - } - } - - _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); - return size(); - } -}; - -#elif defined(BACKWARD_SYSTEM_WINDOWS) - -template <> -class StackTraceImpl : public StackTraceImplHolder { -public: - // We have to load the machine type from the image info - // So we first initialize the resolver, and it tells us this info - void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; } - void set_context(CONTEXT* ctx) { ctx_ = ctx; } - void set_thread_handle(HANDLE handle) { thd_ = handle; } - - NOINLINE - size_t load_here(size_t depth = 32) - { - - CONTEXT localCtx; // used when no context is provided - - if (depth == 0) { - return 0; - } - - if (!ctx_) { - ctx_ = &localCtx; - RtlCaptureContext(ctx_); - } - - if (!thd_) { - thd_ = GetCurrentThread(); - } - - HANDLE process = GetCurrentProcess(); - - STACKFRAME64 s; - memset(&s, 0, sizeof(STACKFRAME64)); - - // TODO: 32 bit context capture - s.AddrStack.Mode = AddrModeFlat; - s.AddrFrame.Mode = AddrModeFlat; - s.AddrPC.Mode = AddrModeFlat; -# ifdef _M_X64 - s.AddrPC.Offset = ctx_->Rip; - s.AddrStack.Offset = ctx_->Rsp; - s.AddrFrame.Offset = ctx_->Rbp; -# else - s.AddrPC.Offset = ctx_->Eip; - s.AddrStack.Offset = ctx_->Esp; - s.AddrFrame.Offset = ctx_->Ebp; -# endif - - if (!machine_type_) { -# ifdef _M_X64 - machine_type_ = IMAGE_FILE_MACHINE_AMD64; -# else - machine_type_ = IMAGE_FILE_MACHINE_I386; -# endif - } - - for (;;) { - // NOTE: this only works if PDBs are already loaded! - SetLastError(0); - if (!StackWalk64( - machine_type_, process, thd_, &s, ctx_, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) - break; - - if (s.AddrReturn.Offset == 0) - break; - - _stacktrace.push_back(reinterpret_cast(s.AddrPC.Offset)); - - if (size() >= depth) - break; - } - - return size(); - } - - size_t load_from(void* addr, size_t depth = 32) - { - load_here(depth + 8); - - for (size_t i = 0; i < _stacktrace.size(); ++i) { - if (_stacktrace[i] == addr) { - skip_n_firsts(i); - break; - } - } - - _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); - return size(); - } - -private: - DWORD machine_type_ = 0; - HANDLE thd_ = 0; - CONTEXT* ctx_ = nullptr; -}; - -#endif - -class StackTrace : public StackTraceImpl { -}; - -/*************** TRACE RESOLVER ***************/ - -template -class TraceResolverImpl; - -#ifdef BACKWARD_SYSTEM_UNKNOWN - -template <> -class TraceResolverImpl { -public: - template - void load_stacktrace(ST&) - { - } - ResolvedTrace resolve(ResolvedTrace t) { return t; } -}; - -#endif - -class TraceResolverImplBase { -protected: - std::string demangle(const char* funcname) { return _demangler.demangle(funcname); } - -private: - details::demangler _demangler; -}; - -#ifdef BACKWARD_SYSTEM_LINUX - -class TraceResolverLinuxBase : public TraceResolverImplBase { -public: - TraceResolverLinuxBase() : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {} - std::string resolve_exec_path(Dl_info& symbol_info) const - { - // mutates symbol_info.dli_fname to be filename to open and returns filename - // to display - if (symbol_info.dli_fname == argv0_) { - // dladdr returns argv[0] in dli_fname for symbols contained in - // the main executable, which is not a valid path if the - // executable was found by a search of the PATH environment - // variable; In that case, we actually open /proc/self/exe, which - // is always the actual executable (even if it was deleted/replaced!) - // but display the path that /proc/self/exe links to. - symbol_info.dli_fname = "/proc/self/exe"; - return exec_path_; - } - else { - return symbol_info.dli_fname; - } - } - -private: - std::string argv0_; - std::string exec_path_; - - static std::string get_argv0() - { - std::string argv0; - std::ifstream ifs("/proc/self/cmdline"); - std::getline(ifs, argv0, '\0'); - return argv0; - } - - static std::string read_symlink(std::string const& symlink_path) - { - std::string path; - path.resize(100); - - while (true) { - ssize_t len = ::readlink(symlink_path.c_str(), &*path.begin(), path.size()); - if (len < 0) { - return ""; - } - if (static_cast(len) == path.size()) { - path.resize(path.size() * 2); - } - else { - path.resize(static_cast(len)); - break; - } - } - - return path; - } -}; - -template -class TraceResolverLinuxImpl; - -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 - -template <> -class TraceResolverLinuxImpl : public TraceResolverLinuxBase { -public: - template - void load_stacktrace(ST& st) - { - using namespace details; - if (st.size() == 0) { - return; - } - _symbols.reset(backtrace_symbols(st.begin(), (int)st.size())); - } - - ResolvedTrace resolve(ResolvedTrace trace) - { - char* filename = _symbols[trace.idx]; - char* funcname = filename; - while (*funcname && *funcname != '(') { - funcname += 1; - } - trace.object_filename.assign( - filename, - funcname); // ok even if funcname is the ending - // \0 (then we assign entire string) - - if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) - funcname += 1; - char* funcname_end = funcname; - while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { - funcname_end += 1; - } - *funcname_end = '\0'; - trace.object_function = this->demangle(funcname); - trace.source.function = trace.object_function; // we cannot do better. - } - return trace; - } - -private: - details::handle _symbols; -}; - -# endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 - -# if BACKWARD_HAS_BFD == 1 - -template <> -class TraceResolverLinuxImpl : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _bfd_loaded(false) {} - - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) - { - Dl_info symbol_info; - - // trace.addr is a virtual address in memory pointing to some code. - // Let's try to find from which loaded object it comes from. - // The loaded object can be yourself btw. - if (!dladdr(trace.addr, &symbol_info)) { - return trace; // dat broken trace... - } - - // Now we get in symbol_info: - // .dli_fname: - // pathname of the shared object that contains the address. - // .dli_fbase: - // where the object is loaded in memory. - // .dli_sname: - // the name of the nearest symbol to trace.addr, we expect a - // function name. - // .dli_saddr: - // the exact address corresponding to .dli_sname. - - if (symbol_info.dli_sname) { - trace.object_function = demangle(symbol_info.dli_sname); - } - - if (!symbol_info.dli_fname) { - return trace; - } - - trace.object_filename = resolve_exec_path(symbol_info); - bfd_fileobject& fobj = load_object_with_bfd(symbol_info.dli_fname); - if (!fobj.handle) { - return trace; // sad, we couldn't load the object :( - } - - find_sym_result* details_selected; // to be filled. - - // trace.addr is the next instruction to be executed after returning - // from the nested stack frame. In C++ this usually relate to the next - // statement right after the function call that leaded to a new stack - // frame. This is not usually what you want to see when printing out a - // stacktrace... - find_sym_result details_call_site = find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); - details_selected = &details_call_site; - -# if BACKWARD_HAS_UNWIND == 0 - // ...this is why we also try to resolve the symbol that is right - // before the return address. If we are lucky enough, we will get the - // line of the function that was called. But if the code is optimized, - // we might get something absolutely not related since the compiler - // can reschedule the return address with inline functions and - // tail-call optimisation (among other things that I don't even know - // or cannot even dream about with my tiny limited brain). - find_sym_result details_adjusted_call_site = - find_symbol_details(fobj, (void*)(uintptr_t(trace.addr) - 1), symbol_info.dli_fbase); - - // In debug mode, we should always get the right thing(TM). - if (details_call_site.found && details_adjusted_call_site.found) { - // Ok, we assume that details_adjusted_call_site is a better estimation. - details_selected = &details_adjusted_call_site; - trace.addr = (void*)(uintptr_t(trace.addr) - 1); - } - - if (details_selected == &details_call_site && details_call_site.found) { - // we have to re-resolve the symbol in order to reset some - // internal state in BFD... so we can call backtrace_inliners - // thereafter... - details_call_site = find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); - } -# endif // BACKWARD_HAS_UNWIND - - if (details_selected->found) { - if (details_selected->filename) { - trace.source.filename = details_selected->filename; - } - trace.source.line = details_selected->line; - - if (details_selected->funcname) { - // this time we get the name of the function where the code is - // located, instead of the function were the address is - // located. In short, if the code was inlined, we get the - // function correspoding to the code. Else we already got in - // trace.function. - trace.source.function = demangle(details_selected->funcname); - - if (!symbol_info.dli_sname) { - // for the case dladdr failed to find the symbol name of - // the function, we might as well try to put something - // here. - trace.object_function = trace.source.function; - } - } - - // Maybe the source of the trace got inlined inside the function - // (trace.source.function). Let's see if we can get all the inlined - // calls along the way up to the initial call site. - trace.inliners = backtrace_inliners(fobj, *details_selected); - -# if 0 - if (trace.inliners.size() == 0) { - // Maybe the trace was not inlined... or maybe it was and we - // are lacking the debug information. Let's try to make the - // world better and see if we can get the line number of the - // function (trace.source.function) now. - // - // We will get the location of where the function start (to be - // exact: the first instruction that really start the - // function), not where the name of the function is defined. - // This can be quite far away from the name of the function - // btw. - // - // If the source of the function is the same as the source of - // the trace, we cannot say if the trace was really inlined or - // not. However, if the filename of the source is different - // between the function and the trace... we can declare it as - // an inliner. This is not 100% accurate, but better than - // nothing. - - if (symbol_info.dli_saddr) { - find_sym_result details = find_symbol_details(fobj, - symbol_info.dli_saddr, - symbol_info.dli_fbase); - - if (details.found) { - ResolvedTrace::SourceLoc diy_inliner; - diy_inliner.line = details.line; - if (details.filename) { - diy_inliner.filename = details.filename; - } - if (details.funcname) { - diy_inliner.function = demangle(details.funcname); - } else { - diy_inliner.function = trace.source.function; - } - if (diy_inliner != trace.source) { - trace.inliners.push_back(diy_inliner); - } - } - } - } -# endif - } - - return trace; - } - -private: - bool _bfd_loaded; - - typedef details::handle> bfd_handle_t; - - typedef details::handle bfd_symtab_t; - - struct bfd_fileobject { - bfd_handle_t handle; - bfd_vma base_addr; - bfd_symtab_t symtab; - bfd_symtab_t dynamic_symtab; - }; - - typedef details::hashtable::type fobj_bfd_map_t; - fobj_bfd_map_t _fobj_bfd_map; - - bfd_fileobject& load_object_with_bfd(const std::string& filename_object) - { - using namespace details; - - if (!_bfd_loaded) { - using namespace details; - bfd_init(); - _bfd_loaded = true; - } - - fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); - if (it != _fobj_bfd_map.end()) { - return it->second; - } - - // this new object is empty for now. - bfd_fileobject& r = _fobj_bfd_map[filename_object]; - - // we do the work temporary in this one; - bfd_handle_t bfd_handle; - - int fd = open(filename_object.c_str(), O_RDONLY); - bfd_handle.reset(bfd_fdopenr(filename_object.c_str(), "default", fd)); - if (!bfd_handle) { - close(fd); - return r; - } - - if (!bfd_check_format(bfd_handle.get(), bfd_object)) { - return r; // not an object? You lose. - } - - if ((bfd_get_file_flags(bfd_handle.get()) & HAS_SYMS) == 0) { - return r; // that's what happen when you forget to compile in debug. - } - - ssize_t symtab_storage_size = bfd_get_symtab_upper_bound(bfd_handle.get()); - - ssize_t dyn_symtab_storage_size = bfd_get_dynamic_symtab_upper_bound(bfd_handle.get()); - - if (symtab_storage_size <= 0 && dyn_symtab_storage_size <= 0) { - return r; // weird, is the file is corrupted? - } - - bfd_symtab_t symtab, dynamic_symtab; - ssize_t symcount = 0, dyn_symcount = 0; - - if (symtab_storage_size > 0) { - symtab.reset(static_cast(malloc(static_cast(symtab_storage_size)))); - symcount = bfd_canonicalize_symtab(bfd_handle.get(), symtab.get()); - } - - if (dyn_symtab_storage_size > 0) { - dynamic_symtab.reset(static_cast(malloc(static_cast(dyn_symtab_storage_size)))); - dyn_symcount = bfd_canonicalize_dynamic_symtab(bfd_handle.get(), dynamic_symtab.get()); - } - - if (symcount <= 0 && dyn_symcount <= 0) { - return r; // damned, that's a stripped file that you got there! - } - - r.handle = move(bfd_handle); - r.symtab = move(symtab); - r.dynamic_symtab = move(dynamic_symtab); - return r; - } - - struct find_sym_result { - bool found; - const char* filename; - const char* funcname; - unsigned int line; - }; - - struct find_sym_context { - TraceResolverLinuxImpl* self; - bfd_fileobject* fobj; - void* addr; - void* base_addr; - find_sym_result result; - }; - - find_sym_result find_symbol_details(bfd_fileobject& fobj, void* addr, void* base_addr) - { - find_sym_context context; - context.self = this; - context.fobj = &fobj; - context.addr = addr; - context.base_addr = base_addr; - context.result.found = false; - bfd_map_over_sections(fobj.handle.get(), &find_in_section_trampoline, static_cast(&context)); - return context.result; - } - - static void find_in_section_trampoline(bfd*, asection* section, void* data) - { - find_sym_context* context = static_cast(data); - context->self->find_in_section( - reinterpret_cast(context->addr), - reinterpret_cast(context->base_addr), - *context->fobj, - section, - context->result); - } - - void find_in_section( - bfd_vma addr, - bfd_vma base_addr, - bfd_fileobject& fobj, - asection* section, - find_sym_result& result) - { - if (result.found) - return; - -# ifdef bfd_get_section_flags - if ((bfd_get_section_flags(fobj.handle.get(), section) & SEC_ALLOC) == 0) -# else - if ((bfd_section_flags(section) & SEC_ALLOC) == 0) -# endif - return; // a debug section is never loaded automatically. - -# ifdef bfd_get_section_vma - bfd_vma sec_addr = bfd_get_section_vma(fobj.handle.get(), section); -# else - bfd_vma sec_addr = bfd_section_vma(section); -# endif -# ifdef bfd_get_section_size - bfd_size_type size = bfd_get_section_size(section); -# else - bfd_size_type size = bfd_section_size(section); -# endif - - // are we in the boundaries of the section? - if (addr < sec_addr || addr >= sec_addr + size) { - addr -= base_addr; // oups, a relocated object, lets try again... - if (addr < sec_addr || addr >= sec_addr + size) { - return; - } - } - -# if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -# endif - if (!result.found && fobj.symtab) { - result.found = bfd_find_nearest_line( - fobj.handle.get(), - section, - fobj.symtab.get(), - addr - sec_addr, - &result.filename, - &result.funcname, - &result.line); - } - - if (!result.found && fobj.dynamic_symtab) { - result.found = bfd_find_nearest_line( - fobj.handle.get(), - section, - fobj.dynamic_symtab.get(), - addr - sec_addr, - &result.filename, - &result.funcname, - &result.line); - } -# if defined(__clang__) -# pragma clang diagnostic pop -# endif - } - - ResolvedTrace::source_locs_t backtrace_inliners(bfd_fileobject& fobj, find_sym_result previous_result) - { - // This function can be called ONLY after a SUCCESSFUL call to - // find_symbol_details. The state is global to the bfd_handle. - ResolvedTrace::source_locs_t results; - while (previous_result.found) { - find_sym_result result; - result.found = bfd_find_inliner_info(fobj.handle.get(), &result.filename, &result.funcname, &result.line); - - if (result.found) /* and not ( - cstrings_eq(previous_result.filename, - result.filename) and - cstrings_eq(previous_result.funcname, result.funcname) - and result.line == previous_result.line - )) */ - { - ResolvedTrace::SourceLoc src_loc; - src_loc.line = result.line; - if (result.filename) { - src_loc.filename = result.filename; - } - if (result.funcname) { - src_loc.function = demangle(result.funcname); - } - results.push_back(src_loc); - } - previous_result = result; - } - return results; - } - - bool cstrings_eq(const char* a, const char* b) - { - if (!a || !b) { - return false; - } - return strcmp(a, b) == 0; - } -}; -# endif // BACKWARD_HAS_BFD == 1 - -# if BACKWARD_HAS_DW == 1 - -template <> -class TraceResolverLinuxImpl : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} - - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) - { - using namespace details; - - Dwarf_Addr trace_addr = (Dwarf_Addr)trace.addr; - - if (!_dwfl_handle_initialized) { - // initialize dwfl... - _dwfl_cb.reset(new Dwfl_Callbacks); - _dwfl_cb->find_elf = &dwfl_linux_proc_find_elf; - _dwfl_cb->find_debuginfo = &dwfl_standard_find_debuginfo; - _dwfl_cb->debuginfo_path = 0; - - _dwfl_handle.reset(dwfl_begin(_dwfl_cb.get())); - _dwfl_handle_initialized = true; - - if (!_dwfl_handle) { - return trace; - } - - // ...from the current process. - dwfl_report_begin(_dwfl_handle.get()); - int r = dwfl_linux_proc_report(_dwfl_handle.get(), getpid()); - dwfl_report_end(_dwfl_handle.get(), NULL, NULL); - if (r < 0) { - return trace; - } - } - - if (!_dwfl_handle) { - return trace; - } - - // find the module (binary object) that contains the trace's address. - // This is not using any debug information, but the addresses ranges of - // all the currently loaded binary object. - Dwfl_Module* mod = dwfl_addrmodule(_dwfl_handle.get(), trace_addr); - if (mod) { - // now that we found it, lets get the name of it, this will be the - // full path to the running binary or one of the loaded library. - const char* module_name = dwfl_module_info(mod, 0, 0, 0, 0, 0, 0, 0); - if (module_name) { - trace.object_filename = module_name; - } - // We also look after the name of the symbol, equal or before this - // address. This is found by walking the symtab. We should get the - // symbol corresponding to the function (mangled) containing the - // address. If the code corresponding to the address was inlined, - // this is the name of the out-most inliner function. - const char* sym_name = dwfl_module_addrname(mod, trace_addr); - if (sym_name) { - trace.object_function = demangle(sym_name); - } - } - - // now let's get serious, and find out the source location (file and - // line number) of the address. - - // This function will look in .debug_aranges for the address and map it - // to the location of the compilation unit DIE in .debug_info and - // return it. - Dwarf_Addr mod_bias = 0; - Dwarf_Die* cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias); - -# if 1 - if (!cudie) { - // Sadly clang does not generate the section .debug_aranges, thus - // dwfl_module_addrdie will fail early. Clang doesn't either set - // the lowpc/highpc/range info for every compilation unit. - // - // So in order to save the world: - // for every compilation unit, we will iterate over every single - // DIEs. Normally functions should have a lowpc/highpc/range, which - // we will use to infer the compilation unit. - - // note that this is probably badly inefficient. - while ((cudie = dwfl_module_nextcu(mod, cudie, &mod_bias))) { - Dwarf_Die die_mem; - Dwarf_Die* fundie = find_fundie_by_pc(cudie, trace_addr - mod_bias, &die_mem); - if (fundie) { - break; - } - } - } -# endif - -//#define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE -# ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE - if (!cudie) { - // If it's still not enough, lets dive deeper in the shit, and try - // to save the world again: for every compilation unit, we will - // load the corresponding .debug_line section, and see if we can - // find our address in it. - - Dwarf_Addr cfi_bias; - Dwarf_CFI* cfi_cache = dwfl_module_eh_cfi(mod, &cfi_bias); - - Dwarf_Addr bias; - while ((cudie = dwfl_module_nextcu(mod, cudie, &bias))) { - if (dwarf_getsrc_die(cudie, trace_addr - bias)) { - - // ...but if we get a match, it might be a false positive - // because our (address - bias) might as well be valid in a - // different compilation unit. So we throw our last card on - // the table and lookup for the address into the .eh_frame - // section. - - handle frame; - dwarf_cfi_addrframe(cfi_cache, trace_addr - cfi_bias, &frame); - if (frame) { - break; - } - } - } - } -# endif - - if (!cudie) { - return trace; // this time we lost the game :/ - } - - // Now that we have a compilation unit DIE, this function will be able - // to load the corresponding section in .debug_line (if not already - // loaded) and hopefully find the source location mapped to our - // address. - Dwarf_Line* srcloc = dwarf_getsrc_die(cudie, trace_addr - mod_bias); - - if (srcloc) { - const char* srcfile = dwarf_linesrc(srcloc, 0, 0); - if (srcfile) { - trace.source.filename = srcfile; - } - int line = 0, col = 0; - dwarf_lineno(srcloc, &line); - dwarf_linecol(srcloc, &col); - trace.source.line = line; - trace.source.col = col; - } - - deep_first_search_by_pc(cudie, trace_addr - mod_bias, inliners_search_cb(trace)); - if (trace.source.function.size() == 0) { - // fallback. - trace.source.function = trace.object_function; - } - - return trace; - } - -private: - typedef details::handle> dwfl_handle_t; - details::handle> _dwfl_cb; - dwfl_handle_t _dwfl_handle; - bool _dwfl_handle_initialized; - - // defined here because in C++98, template function cannot take locally - // defined types... grrr. - struct inliners_search_cb { - void operator()(Dwarf_Die* die) - { - switch (dwarf_tag(die)) { - const char* name; - case DW_TAG_subprogram: - if ((name = dwarf_diename(die))) { - trace.source.function = name; - } - break; - - case DW_TAG_inlined_subroutine: - ResolvedTrace::SourceLoc sloc; - Dwarf_Attribute attr_mem; - - if ((name = dwarf_diename(die))) { - sloc.function = name; - } - if ((name = die_call_file(die))) { - sloc.filename = name; - } - - Dwarf_Word line = 0, col = 0; - dwarf_formudata(dwarf_attr(die, DW_AT_call_line, &attr_mem), &line); - dwarf_formudata(dwarf_attr(die, DW_AT_call_column, &attr_mem), &col); - sloc.line = (unsigned)line; - sloc.col = (unsigned)col; - - trace.inliners.push_back(sloc); - break; - }; - } - ResolvedTrace& trace; - inliners_search_cb(ResolvedTrace& t) : trace(t) {} - }; - - static bool die_has_pc(Dwarf_Die* die, Dwarf_Addr pc) - { - Dwarf_Addr low, high; - - // continuous range - if (dwarf_hasattr(die, DW_AT_low_pc) && dwarf_hasattr(die, DW_AT_high_pc)) { - if (dwarf_lowpc(die, &low) != 0) { - return false; - } - if (dwarf_highpc(die, &high) != 0) { - Dwarf_Attribute attr_mem; - Dwarf_Attribute* attr = dwarf_attr(die, DW_AT_high_pc, &attr_mem); - Dwarf_Word value; - if (dwarf_formudata(attr, &value) != 0) { - return false; - } - high = low + value; - } - return pc >= low && pc < high; - } - - // non-continuous range. - Dwarf_Addr base; - ptrdiff_t offset = 0; - while ((offset = dwarf_ranges(die, offset, &base, &low, &high)) > 0) { - if (pc >= low && pc < high) { - return true; - } - } - return false; - } - - static Dwarf_Die* find_fundie_by_pc(Dwarf_Die* parent_die, Dwarf_Addr pc, Dwarf_Die* result) - { - if (dwarf_child(parent_die, result) != 0) { - return 0; - } - - Dwarf_Die* die = result; - do { - switch (dwarf_tag(die)) { - case DW_TAG_subprogram: - case DW_TAG_inlined_subroutine: - if (die_has_pc(die, pc)) { - return result; - } - }; - bool declaration = false; - Dwarf_Attribute attr_mem; - dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), &declaration); - if (!declaration) { - // let's be curious and look deeper in the tree, - // function are not necessarily at the first level, but - // might be nested inside a namespace, structure etc. - Dwarf_Die die_mem; - Dwarf_Die* indie = find_fundie_by_pc(die, pc, &die_mem); - if (indie) { - *result = die_mem; - return result; - } - } - } while (dwarf_siblingof(die, result) == 0); - return 0; - } - - template - static bool deep_first_search_by_pc(Dwarf_Die* parent_die, Dwarf_Addr pc, CB cb) - { - Dwarf_Die die_mem; - if (dwarf_child(parent_die, &die_mem) != 0) { - return false; - } - - bool branch_has_pc = false; - Dwarf_Die* die = &die_mem; - do { - bool declaration = false; - Dwarf_Attribute attr_mem; - dwarf_formflag(dwarf_attr(die, DW_AT_declaration, &attr_mem), &declaration); - if (!declaration) { - // let's be curious and look deeper in the tree, function are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - branch_has_pc = deep_first_search_by_pc(die, pc, cb); - } - if (!branch_has_pc) { - branch_has_pc = die_has_pc(die, pc); - } - if (branch_has_pc) { - cb(die); - } - } while (dwarf_siblingof(die, &die_mem) == 0); - return branch_has_pc; - } - - static const char* die_call_file(Dwarf_Die* die) - { - Dwarf_Attribute attr_mem; - Dwarf_Sword file_idx = 0; - - dwarf_formsdata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); - - if (file_idx == 0) { - return 0; - } - - Dwarf_Die die_mem; - Dwarf_Die* cudie = dwarf_diecu(die, &die_mem, 0, 0); - if (!cudie) { - return 0; - } - - Dwarf_Files* files = 0; - size_t nfiles; - dwarf_getsrcfiles(cudie, &files, &nfiles); - if (!files) { - return 0; - } - - return dwarf_filesrc(files, file_idx, 0, 0); - } -}; -# endif // BACKWARD_HAS_DW == 1 - -# if BACKWARD_HAS_DWARF == 1 - -template <> -class TraceResolverLinuxImpl : public TraceResolverLinuxBase { -public: - TraceResolverLinuxImpl() : _dwarf_loaded(false) {} - - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) - { - // trace.addr is a virtual address in memory pointing to some code. - // Let's try to find from which loaded object it comes from. - // The loaded object can be yourself btw. - - Dl_info symbol_info; - int dladdr_result = 0; -# if defined(__GLIBC__) - link_map* link_map; - // We request the link map so we can get information about offsets - dladdr_result = dladdr1(trace.addr, &symbol_info, reinterpret_cast(&link_map), RTLD_DL_LINKMAP); -# else - // Android doesn't have dladdr1. Don't use the linker map. - dladdr_result = dladdr(trace.addr, &symbol_info); -# endif - if (!dladdr_result) { - return trace; // dat broken trace... - } - - // Now we get in symbol_info: - // .dli_fname: - // pathname of the shared object that contains the address. - // .dli_fbase: - // where the object is loaded in memory. - // .dli_sname: - // the name of the nearest symbol to trace.addr, we expect a - // function name. - // .dli_saddr: - // the exact address corresponding to .dli_sname. - // - // And in link_map: - // .l_addr: - // difference between the address in the ELF file and the address - // in memory - // l_name: - // absolute pathname where the object was found - - if (symbol_info.dli_sname) { - trace.object_function = demangle(symbol_info.dli_sname); - } - - if (!symbol_info.dli_fname) { - return trace; - } - - trace.object_filename = resolve_exec_path(symbol_info); - dwarf_fileobject& fobj = load_object_with_dwarf(symbol_info.dli_fname); - if (!fobj.dwarf_handle) { - return trace; // sad, we couldn't load the object :( - } - -# if defined(__GLIBC__) - // Convert the address to a module relative one by looking at - // the module's loading address in the link map - Dwarf_Addr address = reinterpret_cast(trace.addr) - reinterpret_cast(link_map->l_addr); -# else - Dwarf_Addr address = reinterpret_cast(trace.addr); -# endif - - if (trace.object_function.empty()) { - symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address); - - if (it != fobj.symbol_cache.end()) { - if (it->first != address) { - if (it != fobj.symbol_cache.begin()) { - --it; - } - } - trace.object_function = demangle(it->second.c_str()); - } - } - - // Get the Compilation Unit DIE for the address - Dwarf_Die die = find_die(fobj, address); - - if (!die) { - return trace; // this time we lost the game :/ - } - - // libdwarf doesn't give us direct access to its objects, it always - // allocates a copy for the caller. We keep that copy alive in a cache - // and we deallocate it later when it's no longer required. - die_cache_entry& die_object = get_die_cache(fobj, die); - if (die_object.isEmpty()) - return trace; // We have no line section for this DIE - - die_linemap_t::iterator it = die_object.line_section.lower_bound(address); - - if (it != die_object.line_section.end()) { - if (it->first != address) { - if (it == die_object.line_section.begin()) { - // If we are on the first item of the line section - // but the address does not match it means that - // the address is below the range of the DIE. Give up. - return trace; - } - else { - --it; - } - } - } - else { - return trace; // We didn't find the address. - } - - // Get the Dwarf_Line that the address points to and call libdwarf - // to get source file, line and column info. - Dwarf_Line line = die_object.line_buffer[it->second]; - Dwarf_Error error = DW_DLE_NE; - - char* filename; - if (dwarf_linesrc(line, &filename, &error) == DW_DLV_OK) { - trace.source.filename = std::string(filename); - dwarf_dealloc(fobj.dwarf_handle.get(), filename, DW_DLA_STRING); - } - - Dwarf_Unsigned number = 0; - if (dwarf_lineno(line, &number, &error) == DW_DLV_OK) { - trace.source.line = number; - } - else { - trace.source.line = 0; - } - - if (dwarf_lineoff_b(line, &number, &error) == DW_DLV_OK) { - trace.source.col = number; - } - else { - trace.source.col = 0; - } - - std::vector namespace_stack; - deep_first_search_by_pc(fobj, die, address, namespace_stack, inliners_search_cb(trace, fobj, die)); - - dwarf_dealloc(fobj.dwarf_handle.get(), die, DW_DLA_DIE); - - return trace; - } - -public: - static int close_dwarf(Dwarf_Debug dwarf) { return dwarf_finish(dwarf, NULL); } - -private: - bool _dwarf_loaded; - - typedef details::handle> dwarf_file_t; - - typedef details::handle> dwarf_elf_t; - - typedef details::handle> dwarf_handle_t; - - typedef std::map die_linemap_t; - - typedef std::map die_specmap_t; - - struct die_cache_entry { - die_specmap_t spec_section; - die_linemap_t line_section; - Dwarf_Line* line_buffer; - Dwarf_Signed line_count; - Dwarf_Line_Context line_context; - - inline bool isEmpty() - { - return line_buffer == NULL || line_count == 0 || line_context == NULL || line_section.empty(); - } - - die_cache_entry() : line_buffer(0), line_count(0), line_context(0) {} - - ~die_cache_entry() - { - if (line_context) { - dwarf_srclines_dealloc_b(line_context); - } - } - }; - - typedef std::map die_cache_t; - - typedef std::map symbol_cache_t; - - struct dwarf_fileobject { - dwarf_file_t file_handle; - dwarf_elf_t elf_handle; - dwarf_handle_t dwarf_handle; - symbol_cache_t symbol_cache; - - // Die cache - die_cache_t die_cache; - die_cache_entry* current_cu; - }; - - typedef details::hashtable::type fobj_dwarf_map_t; - fobj_dwarf_map_t _fobj_dwarf_map; - - static bool cstrings_eq(const char* a, const char* b) - { - if (!a || !b) { - return false; - } - return strcmp(a, b) == 0; - } - - dwarf_fileobject& load_object_with_dwarf(const std::string& filename_object) - { - - if (!_dwarf_loaded) { - // Set the ELF library operating version - // If that fails there's nothing we can do - _dwarf_loaded = elf_version(EV_CURRENT) != EV_NONE; - } - - fobj_dwarf_map_t::iterator it = _fobj_dwarf_map.find(filename_object); - if (it != _fobj_dwarf_map.end()) { - return it->second; - } - - // this new object is empty for now - dwarf_fileobject& r = _fobj_dwarf_map[filename_object]; - - dwarf_file_t file_handle; - file_handle.reset(open(filename_object.c_str(), O_RDONLY)); - if (file_handle.get() < 0) { - return r; - } - - // Try to get an ELF handle. We need to read the ELF sections - // because we want to see if there is a .gnu_debuglink section - // that points to a split debug file - dwarf_elf_t elf_handle; - elf_handle.reset(elf_begin(file_handle.get(), ELF_C_READ, NULL)); - if (!elf_handle) { - return r; - } - - const char* e_ident = elf_getident(elf_handle.get(), 0); - if (!e_ident) { - return r; - } - - // Get the number of sections - // We use the new APIs as elf_getshnum is deprecated - size_t shdrnum = 0; - if (elf_getshdrnum(elf_handle.get(), &shdrnum) == -1) { - return r; - } - - // Get the index to the string section - size_t shdrstrndx = 0; - if (elf_getshdrstrndx(elf_handle.get(), &shdrstrndx) == -1) { - return r; - } - - std::string debuglink; - // Iterate through the ELF sections to try to get a gnu_debuglink - // note and also to cache the symbol table. - // We go the preprocessor way to avoid having to create templated - // classes or using gelf (which might throw a compiler error if 64 bit - // is not supported -# define ELF_GET_DATA(ARCH) \ - Elf_Scn* elf_section = 0; \ - Elf_Data* elf_data = 0; \ - Elf##ARCH##_Shdr* section_header = 0; \ - Elf_Scn* symbol_section = 0; \ - size_t symbol_count = 0; \ - size_t symbol_strings = 0; \ - Elf##ARCH##_Sym* symbol = 0; \ - const char* section_name = 0; \ - \ - while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ - section_header = elf##ARCH##_getshdr(elf_section); \ - if (section_header == NULL) { \ - return r; \ - } \ - \ - if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, section_header->sh_name)) == NULL) { \ - return r; \ - } \ - \ - if (cstrings_eq(section_name, ".gnu_debuglink")) { \ - elf_data = elf_getdata(elf_section, NULL); \ - if (elf_data && elf_data->d_size > 0) { \ - debuglink = std::string(reinterpret_cast(elf_data->d_buf)); \ - } \ - } \ - \ - switch (section_header->sh_type) { \ - case SHT_SYMTAB: \ - symbol_section = elf_section; \ - symbol_count = section_header->sh_size / section_header->sh_entsize; \ - symbol_strings = section_header->sh_link; \ - break; \ - \ - /* We use .dynsyms as a last resort, we prefer .symtab */ \ - case SHT_DYNSYM: \ - if (!symbol_section) { \ - symbol_section = elf_section; \ - symbol_count = section_header->sh_size / section_header->sh_entsize; \ - symbol_strings = section_header->sh_link; \ - } \ - break; \ - } \ - } \ - \ - if (symbol_section && symbol_count && symbol_strings) { \ - elf_data = elf_getdata(symbol_section, NULL); \ - symbol = reinterpret_cast(elf_data->d_buf); \ - for (size_t i = 0; i < symbol_count; ++i) { \ - int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ - if (type == STT_FUNC && symbol->st_value > 0) { \ - r.symbol_cache[symbol->st_value] = \ - std::string(elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ - } \ - ++symbol; \ - } \ - } - - if (e_ident[EI_CLASS] == ELFCLASS32) { - ELF_GET_DATA(32) - } - else if (e_ident[EI_CLASS] == ELFCLASS64) { - // libelf might have been built without 64 bit support -# if __LIBELF64 - ELF_GET_DATA(64) -# endif - } - - if (!debuglink.empty()) { - // We have a debuglink section! Open an elf instance on that - // file instead. If we can't open the file, then return - // the elf handle we had already opened. - dwarf_file_t debuglink_file; - debuglink_file.reset(open(debuglink.c_str(), O_RDONLY)); - if (debuglink_file.get() > 0) { - dwarf_elf_t debuglink_elf; - debuglink_elf.reset(elf_begin(debuglink_file.get(), ELF_C_READ, NULL)); - - // If we have a valid elf handle, return the new elf handle - // and file handle and discard the original ones - if (debuglink_elf) { - elf_handle = move(debuglink_elf); - file_handle = move(debuglink_file); - } - } - } - - // Ok, we have a valid ELF handle, let's try to get debug symbols - Dwarf_Debug dwarf_debug; - Dwarf_Error error = DW_DLE_NE; - dwarf_handle_t dwarf_handle; - - int dwarf_result = dwarf_elf_init(elf_handle.get(), DW_DLC_READ, NULL, NULL, &dwarf_debug, &error); - - // We don't do any special handling for DW_DLV_NO_ENTRY specially. - // If we get an error, or the file doesn't have debug information - // we just return. - if (dwarf_result != DW_DLV_OK) { - return r; - } - - dwarf_handle.reset(dwarf_debug); - - r.file_handle = move(file_handle); - r.elf_handle = move(elf_handle); - r.dwarf_handle = move(dwarf_handle); - - return r; - } - - die_cache_entry& get_die_cache(dwarf_fileobject& fobj, Dwarf_Die die) - { - Dwarf_Error error = DW_DLE_NE; - - // Get the die offset, we use it as the cache key - Dwarf_Off die_offset; - if (dwarf_dieoffset(die, &die_offset, &error) != DW_DLV_OK) { - die_offset = 0; - } - - die_cache_t::iterator it = fobj.die_cache.find(die_offset); - - if (it != fobj.die_cache.end()) { - fobj.current_cu = &it->second; - return it->second; - } - - die_cache_entry& de = fobj.die_cache[die_offset]; - fobj.current_cu = &de; - - Dwarf_Addr line_addr; - Dwarf_Small table_count; - - // The addresses in the line section are not fully sorted (they might - // be sorted by block of code belonging to the same file), which makes - // it necessary to do so before searching is possible. - // - // As libdwarf allocates a copy of everything, let's get the contents - // of the line section and keep it around. We also create a map of - // program counter to line table indices so we can search by address - // and get the line buffer index. - // - // To make things more difficult, the same address can span more than - // one line, so we need to keep the index pointing to the first line - // by using insert instead of the map's [ operator. - - // Get the line context for the DIE - if (dwarf_srclines_b(die, 0, &table_count, &de.line_context, &error) == DW_DLV_OK) { - // Get the source lines for this line context, to be deallocated - // later - if (dwarf_srclines_from_linecontext(de.line_context, &de.line_buffer, &de.line_count, &error) == - DW_DLV_OK) { - - // Add all the addresses to our map - for (int i = 0; i < de.line_count; i++) { - if (dwarf_lineaddr(de.line_buffer[i], &line_addr, &error) != DW_DLV_OK) { - line_addr = 0; - } - de.line_section.insert(std::pair(line_addr, i)); - } - } - } - - // For each CU, cache the function DIEs that contain the - // DW_AT_specification attribute. When building with -g3 the function - // DIEs are separated in declaration and specification, with the - // declaration containing only the name and parameters and the - // specification the low/high pc and other compiler attributes. - // - // We cache those specifications so we don't skip over the declarations, - // because they have no pc, and we can do namespace resolution for - // DWARF function names. - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Die current_die = 0; - if (dwarf_child(die, ¤t_die, &error) == DW_DLV_OK) { - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - if (tag_value == DW_TAG_subprogram || tag_value == DW_TAG_inlined_subroutine) { - - Dwarf_Bool has_attr = 0; - if (dwarf_hasattr(current_die, DW_AT_specification, &has_attr, &error) == DW_DLV_OK) { - if (has_attr) { - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_specification, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Off spec_offset = 0; - if (dwarf_formref(attr_mem, &spec_offset, &error) == DW_DLV_OK) { - Dwarf_Off spec_die_offset; - if (dwarf_dieoffset(current_die, &spec_die_offset, &error) == DW_DLV_OK) { - de.spec_section[spec_offset] = spec_die_offset; - } - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - } - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - break; - } - else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - } - return de; - } - - static Dwarf_Die get_referenced_die(Dwarf_Debug dwarf, Dwarf_Die die, Dwarf_Half attr, bool global) - { - Dwarf_Error error = DW_DLE_NE; - Dwarf_Attribute attr_mem; - - Dwarf_Die found_die = NULL; - if (dwarf_attr(die, attr, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Off offset; - int result = 0; - if (global) { - result = dwarf_global_formref(attr_mem, &offset, &error); - } - else { - result = dwarf_formref(attr_mem, &offset, &error); - } - - if (result == DW_DLV_OK) { - if (dwarf_offdie(dwarf, offset, &found_die, &error) != DW_DLV_OK) { - found_die = NULL; - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - return found_die; - } - - static std::string get_referenced_die_name(Dwarf_Debug dwarf, Dwarf_Die die, Dwarf_Half attr, bool global) - { - Dwarf_Error error = DW_DLE_NE; - std::string value; - - Dwarf_Die found_die = get_referenced_die(dwarf, die, attr, global); - - if (found_die) { - char* name; - if (dwarf_diename(found_die, &name, &error) == DW_DLV_OK) { - if (name) { - value = std::string(name); - } - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } - dwarf_dealloc(dwarf, found_die, DW_DLA_DIE); - } - - return value; - } - - // Returns a spec DIE linked to the passed one. The caller should - // deallocate the DIE - static Dwarf_Die get_spec_die(dwarf_fileobject& fobj, Dwarf_Die die) - { - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Off die_offset; - if (fobj.current_cu && dwarf_die_CU_offset(die, &die_offset, &error) == DW_DLV_OK) { - die_specmap_t::iterator it = fobj.current_cu->spec_section.find(die_offset); - - // If we have a DIE that completes the current one, check if - // that one has the pc we are looking for - if (it != fobj.current_cu->spec_section.end()) { - Dwarf_Die spec_die = 0; - if (dwarf_offdie(dwarf, it->second, &spec_die, &error) == DW_DLV_OK) { - return spec_die; - } - } - } - - // Maybe we have an abstract origin DIE with the function information? - return get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_abstract_origin, true); - } - - static bool die_has_pc(dwarf_fileobject& fobj, Dwarf_Die die, Dwarf_Addr pc) - { - Dwarf_Addr low_pc = 0, high_pc = 0; - Dwarf_Half high_pc_form = 0; - Dwarf_Form_Class return_class; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - bool has_lowpc = false; - bool has_highpc = false; - bool has_ranges = false; - - if (dwarf_lowpc(die, &low_pc, &error) == DW_DLV_OK) { - // If we have a low_pc check if there is a high pc. - // If we don't have a high pc this might mean we have a base - // address for the ranges list or just an address. - has_lowpc = true; - - if (dwarf_highpc_b(die, &high_pc, &high_pc_form, &return_class, &error) == DW_DLV_OK) { - // We do have a high pc. In DWARF 4+ this is an offset from the - // low pc, but in earlier versions it's an absolute address. - - has_highpc = true; - // In DWARF 2/3 this would be a DW_FORM_CLASS_ADDRESS - if (return_class == DW_FORM_CLASS_CONSTANT) { - high_pc = low_pc + high_pc; - } - - // We have low and high pc, check if our address - // is in that range - return pc >= low_pc && pc < high_pc; - } - } - else { - // Reset the low_pc, in case dwarf_lowpc failing set it to some - // undefined value. - low_pc = 0; - } - - // Check if DW_AT_ranges is present and search for the PC in the - // returned ranges list. We always add the low_pc, as it not set it will - // be 0, in case we had a DW_AT_low_pc and DW_AT_ranges pair - bool result = false; - - Dwarf_Attribute attr; - if (dwarf_attr(die, DW_AT_ranges, &attr, &error) == DW_DLV_OK) { - - Dwarf_Off offset; - if (dwarf_global_formref(attr, &offset, &error) == DW_DLV_OK) { - Dwarf_Ranges* ranges; - Dwarf_Signed ranges_count = 0; - Dwarf_Unsigned byte_count = 0; - - if (dwarf_get_ranges_a(dwarf, offset, die, &ranges, &ranges_count, &byte_count, &error) == DW_DLV_OK) { - has_ranges = ranges_count != 0; - for (int i = 0; i < ranges_count; i++) { - if (ranges[i].dwr_addr1 != 0 && pc >= ranges[i].dwr_addr1 + low_pc && - pc < ranges[i].dwr_addr2 + low_pc) { - result = true; - break; - } - } - dwarf_ranges_dealloc(dwarf, ranges, ranges_count); - } - } - } - - // Last attempt. We might have a single address set as low_pc. - if (!result && low_pc != 0 && pc == low_pc) { - result = true; - } - - // If we don't have lowpc, highpc and ranges maybe this DIE is a - // declaration that relies on a DW_AT_specification DIE that happens - // later. Use the specification cache we filled when we loaded this CU. - if (!result && (!has_lowpc && !has_highpc && !has_ranges)) { - Dwarf_Die spec_die = get_spec_die(fobj, die); - if (spec_die) { - result = die_has_pc(fobj, spec_die, pc); - dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); - } - } - - return result; - } - - static void get_type(Dwarf_Debug dwarf, Dwarf_Die die, std::string& type) - { - Dwarf_Error error = DW_DLE_NE; - - Dwarf_Die child = 0; - if (dwarf_child(die, &child, &error) == DW_DLV_OK) { - get_type(dwarf, child, type); - } - - if (child) { - type.insert(0, "::"); - dwarf_dealloc(dwarf, child, DW_DLA_DIE); - } - - char* name; - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - type.insert(0, std::string(name)); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } - else { - type.insert(0, ""); - } - } - - static std::string get_type_by_signature(Dwarf_Debug dwarf, Dwarf_Die die) - { - Dwarf_Error error = DW_DLE_NE; - - Dwarf_Sig8 signature; - Dwarf_Bool has_attr = 0; - if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) { - if (has_attr) { - Dwarf_Attribute attr_mem; - if (dwarf_attr(die, DW_AT_signature, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formsig8(attr_mem, &signature, &error) != DW_DLV_OK) { - return std::string(""); - } - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - } - - Dwarf_Unsigned next_cu_header; - Dwarf_Sig8 tu_signature; - std::string result; - bool found = false; - - while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, &tu_signature, 0, &next_cu_header, 0, &error) == - DW_DLV_OK) { - - if (strncmp(signature.signature, tu_signature.signature, 8) == 0) { - Dwarf_Die type_cu_die = 0; - if (dwarf_siblingof_b(dwarf, 0, 0, &type_cu_die, &error) == DW_DLV_OK) { - Dwarf_Die child_die = 0; - if (dwarf_child(type_cu_die, &child_die, &error) == DW_DLV_OK) { - get_type(dwarf, child_die, result); - found = !result.empty(); - dwarf_dealloc(dwarf, child_die, DW_DLA_DIE); - } - dwarf_dealloc(dwarf, type_cu_die, DW_DLA_DIE); - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 0, 0, 0, 0, 0, 0, 0, 0, 0, &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Unfortunately, libdwarf's - // next_cu_header API keeps its own iterator per Dwarf_Debug - // that can't be reset. We need to keep fetching elements until - // the end. - } - } - else { - // If we couldn't resolve the type just print out the signature - std::ostringstream string_stream; - string_stream << "<0x" << std::hex << std::setfill('0'); - for (int i = 0; i < 8; ++i) { - string_stream << std::setw(2) << std::hex << (int)(unsigned char)(signature.signature[i]); - } - string_stream << ">"; - result = string_stream.str(); - } - return result; - } - - struct type_context_t { - bool is_const; - bool is_typedef; - bool has_type; - bool has_name; - std::string text; - - type_context_t() : is_const(false), is_typedef(false), has_type(false), has_name(false) {} - }; - - // Types are resolved from right to left: we get the variable name first - // and then all specifiers (like const or pointer) in a chain of DW_AT_type - // DIEs. Call this function recursively until we get a complete type - // string. - static void set_parameter_string(dwarf_fileobject& fobj, Dwarf_Die die, type_context_t& context) - { - char* name; - Dwarf_Error error = DW_DLE_NE; - - // typedefs contain also the base type, so we skip it and only - // print the typedef name - if (!context.is_typedef) { - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - if (!context.text.empty()) { - context.text.insert(0, " "); - } - context.text.insert(0, std::string(name)); - dwarf_dealloc(fobj.dwarf_handle.get(), name, DW_DLA_STRING); - } - } - else { - context.is_typedef = false; - context.has_type = true; - if (context.is_const) { - context.text.insert(0, "const "); - context.is_const = false; - } - } - - bool next_type_is_const = false; - bool is_keyword = true; - - Dwarf_Half tag = 0; - Dwarf_Bool has_attr = 0; - if (dwarf_tag(die, &tag, &error) == DW_DLV_OK) { - switch (tag) { - case DW_TAG_structure_type: - case DW_TAG_union_type: - case DW_TAG_class_type: - case DW_TAG_enumeration_type: - context.has_type = true; - if (dwarf_hasattr(die, DW_AT_signature, &has_attr, &error) == DW_DLV_OK) { - // If we have a signature it means the type is defined - // in .debug_types, so we need to load the DIE pointed - // at by the signature and resolve it - if (has_attr) { - std::string type = get_type_by_signature(fobj.dwarf_handle.get(), die); - if (context.is_const) - type.insert(0, "const "); - - if (!context.text.empty()) - context.text.insert(0, " "); - context.text.insert(0, type); - } - - // Treat enums like typedefs, and skip printing its - // base type - context.is_typedef = (tag == DW_TAG_enumeration_type); - } - break; - case DW_TAG_const_type: - next_type_is_const = true; - break; - case DW_TAG_pointer_type: - context.text.insert(0, "*"); - break; - case DW_TAG_reference_type: - context.text.insert(0, "&"); - break; - case DW_TAG_restrict_type: - context.text.insert(0, "restrict "); - break; - case DW_TAG_rvalue_reference_type: - context.text.insert(0, "&&"); - break; - case DW_TAG_volatile_type: - context.text.insert(0, "volatile "); - break; - case DW_TAG_typedef: - // Propagate the const-ness to the next type - // as typedefs are linked to its base type - next_type_is_const = context.is_const; - context.is_typedef = true; - context.has_type = true; - break; - case DW_TAG_base_type: - context.has_type = true; - break; - case DW_TAG_formal_parameter: - context.has_name = true; - break; - default: - is_keyword = false; - break; - } - } - - if (!is_keyword && context.is_const) { - context.text.insert(0, "const "); - } - - context.is_const = next_type_is_const; - - Dwarf_Die ref = get_referenced_die(fobj.dwarf_handle.get(), die, DW_AT_type, true); - if (ref) { - set_parameter_string(fobj, ref, context); - dwarf_dealloc(fobj.dwarf_handle.get(), ref, DW_DLA_DIE); - } - - if (!context.has_type && context.has_name) { - context.text.insert(0, "void "); - context.has_type = true; - } - } - - // Resolve the function return type and parameters - static void set_function_parameters( - std::string& function_name, - std::vector& ns, - dwarf_fileobject& fobj, - Dwarf_Die die) - { - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Die current_die = 0; - std::string parameters; - bool has_spec = true; - // Check if we have a spec DIE. If we do we use it as it contains - // more information, like parameter names. - Dwarf_Die spec_die = get_spec_die(fobj, die); - if (!spec_die) { - has_spec = false; - spec_die = die; - } - - std::vector::const_iterator it = ns.begin(); - std::string ns_name; - for (it = ns.begin(); it < ns.end(); ++it) { - ns_name.append(*it).append("::"); - } - - if (!ns_name.empty()) { - function_name.insert(0, ns_name); - } - - // See if we have a function return type. It can be either on the - // current die or in its spec one (usually true for inlined functions) - std::string return_type = get_referenced_die_name(dwarf, die, DW_AT_type, true); - if (return_type.empty()) { - return_type = get_referenced_die_name(dwarf, spec_die, DW_AT_type, true); - } - if (!return_type.empty()) { - return_type.append(" "); - function_name.insert(0, return_type); - } - - if (dwarf_child(spec_die, ¤t_die, &error) == DW_DLV_OK) { - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - if (tag_value == DW_TAG_formal_parameter) { - // Ignore artificial (ie, compiler generated) parameters - bool is_artificial = false; - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_artificial, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - is_artificial = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!is_artificial) { - type_context_t context; - set_parameter_string(fobj, current_die, context); - - if (parameters.empty()) { - parameters.append("("); - } - else { - parameters.append(", "); - } - parameters.append(context.text); - } - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - break; - } - else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - } - if (parameters.empty()) - parameters = "("; - parameters.append(")"); - - // If we got a spec DIE we need to deallocate it - if (has_spec) - dwarf_dealloc(dwarf, spec_die, DW_DLA_DIE); - - function_name.append(parameters); - } - - // defined here because in C++98, template function cannot take locally - // defined types... grrr. - struct inliners_search_cb { - void operator()(Dwarf_Die die, std::vector& ns) - { - Dwarf_Error error = DW_DLE_NE; - Dwarf_Half tag_value; - Dwarf_Attribute attr_mem; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - - dwarf_tag(die, &tag_value, &error); - - switch (tag_value) { - char* name; - case DW_TAG_subprogram: - if (!trace.source.function.empty()) - break; - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - trace.source.function = std::string(name); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } - else { - // We don't have a function name in this DIE. - // Check if there is a referenced non-defining - // declaration. - trace.source.function = get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); - if (trace.source.function.empty()) { - trace.source.function = get_referenced_die_name(dwarf, die, DW_AT_specification, true); - } - } - - // Append the function parameters, if available - set_function_parameters(trace.source.function, ns, fobj, die); - - // If the object function name is empty, it's possible that - // there is no dynamic symbol table (maybe the executable - // was stripped or not built with -rdynamic). See if we have - // a DWARF linkage name to use instead. We try both - // linkage_name and MIPS_linkage_name because the MIPS tag - // was the unofficial one until it was adopted in DWARF4. - // Old gcc versions generate MIPS_linkage_name - if (trace.object_function.empty()) { - details::demangler demangler; - - if (dwarf_attr(die, DW_AT_linkage_name, &attr_mem, &error) != DW_DLV_OK) { - if (dwarf_attr(die, DW_AT_MIPS_linkage_name, &attr_mem, &error) != DW_DLV_OK) { - break; - } - } - - char* linkage; - if (dwarf_formstring(attr_mem, &linkage, &error) == DW_DLV_OK) { - trace.object_function = demangler.demangle(linkage); - dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); - } - dwarf_dealloc(dwarf, name, DW_DLA_ATTR); - } - break; - - case DW_TAG_inlined_subroutine: - ResolvedTrace::SourceLoc sloc; - - if (dwarf_diename(die, &name, &error) == DW_DLV_OK) { - sloc.function = std::string(name); - dwarf_dealloc(dwarf, name, DW_DLA_STRING); - } - else { - // We don't have a name for this inlined DIE, it could - // be that there is an abstract origin instead. - // Get the DW_AT_abstract_origin value, which is a - // reference to the source DIE and try to get its name - sloc.function = get_referenced_die_name(dwarf, die, DW_AT_abstract_origin, true); - } - - set_function_parameters(sloc.function, ns, fobj, die); - - std::string file = die_call_file(dwarf, die, cu_die); - if (!file.empty()) - sloc.filename = file; - - Dwarf_Unsigned number = 0; - if (dwarf_attr(die, DW_AT_call_line, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { - sloc.line = number; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (dwarf_attr(die, DW_AT_call_column, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formudata(attr_mem, &number, &error) == DW_DLV_OK) { - sloc.col = number; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - trace.inliners.push_back(sloc); - break; - }; - } - ResolvedTrace& trace; - dwarf_fileobject& fobj; - Dwarf_Die cu_die; - inliners_search_cb(ResolvedTrace& t, dwarf_fileobject& f, Dwarf_Die c) : trace(t), fobj(f), cu_die(c) {} - }; - - static Dwarf_Die find_fundie_by_pc(dwarf_fileobject& fobj, Dwarf_Die parent_die, Dwarf_Addr pc, Dwarf_Die result) - { - Dwarf_Die current_die = 0; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - - if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { - return NULL; - } - - for (;;) { - Dwarf_Die sibling_die = 0; - Dwarf_Half tag_value; - dwarf_tag(current_die, &tag_value, &error); - - switch (tag_value) { - case DW_TAG_subprogram: - case DW_TAG_inlined_subroutine: - if (die_has_pc(fobj, current_die, pc)) { - return current_die; - } - }; - bool declaration = false; - Dwarf_Attribute attr_mem; - if (dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - declaration = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!declaration) { - // let's be curious and look deeper in the tree, functions are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - Dwarf_Die die_mem = 0; - Dwarf_Die indie = find_fundie_by_pc(fobj, current_die, pc, die_mem); - if (indie) { - result = die_mem; - return result; - } - } - - int res = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (res == DW_DLV_ERROR) { - return NULL; - } - else if (res == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != parent_die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - current_die = sibling_die; - } - return NULL; - } - - template - static bool deep_first_search_by_pc( - dwarf_fileobject& fobj, - Dwarf_Die parent_die, - Dwarf_Addr pc, - std::vector& ns, - CB cb) - { - Dwarf_Die current_die = 0; - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - - if (dwarf_child(parent_die, ¤t_die, &error) != DW_DLV_OK) { - return false; - } - - bool branch_has_pc = false; - bool has_namespace = false; - for (;;) { - Dwarf_Die sibling_die = 0; - - Dwarf_Half tag; - if (dwarf_tag(current_die, &tag, &error) == DW_DLV_OK) { - if (tag == DW_TAG_namespace || tag == DW_TAG_class_type) { - char* ns_name = NULL; - if (dwarf_diename(current_die, &ns_name, &error) == DW_DLV_OK) { - if (ns_name) { - ns.push_back(std::string(ns_name)); - } - else { - ns.push_back(""); - } - dwarf_dealloc(dwarf, ns_name, DW_DLA_STRING); - } - else { - ns.push_back(""); - } - has_namespace = true; - } - } - - bool declaration = false; - Dwarf_Attribute attr_mem; - if (tag != DW_TAG_class_type && - dwarf_attr(current_die, DW_AT_declaration, &attr_mem, &error) == DW_DLV_OK) { - Dwarf_Bool flag = 0; - if (dwarf_formflag(attr_mem, &flag, &error) == DW_DLV_OK) { - declaration = flag != 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - } - - if (!declaration) { - // let's be curious and look deeper in the tree, function are - // not necessarily at the first level, but might be nested - // inside a namespace, structure, a function, an inlined - // function etc. - branch_has_pc = deep_first_search_by_pc(fobj, current_die, pc, ns, cb); - } - - if (!branch_has_pc) { - branch_has_pc = die_has_pc(fobj, current_die, pc); - } - - if (branch_has_pc) { - cb(current_die, ns); - } - - int result = dwarf_siblingof(dwarf, current_die, &sibling_die, &error); - if (result == DW_DLV_ERROR) { - return false; - } - else if (result == DW_DLV_NO_ENTRY) { - break; - } - - if (current_die != parent_die) { - dwarf_dealloc(dwarf, current_die, DW_DLA_DIE); - current_die = 0; - } - - if (has_namespace) { - has_namespace = false; - ns.pop_back(); - } - current_die = sibling_die; - } - - if (has_namespace) { - ns.pop_back(); - } - return branch_has_pc; - } - - static std::string die_call_file(Dwarf_Debug dwarf, Dwarf_Die die, Dwarf_Die cu_die) - { - Dwarf_Attribute attr_mem; - Dwarf_Error error = DW_DLE_NE; - Dwarf_Signed file_index; - - std::string file; - - if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formsdata(attr_mem, &file_index, &error) != DW_DLV_OK) { - file_index = 0; - } - dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); - - if (file_index == 0) { - return file; - } - - char** srcfiles = 0; - Dwarf_Signed file_count = 0; - if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { - if (file_index <= file_count) - file = std::string(srcfiles[file_index - 1]); - - // Deallocate all strings! - for (int i = 0; i < file_count; ++i) { - dwarf_dealloc(dwarf, srcfiles[i], DW_DLA_STRING); - } - dwarf_dealloc(dwarf, srcfiles, DW_DLA_LIST); - } - } - return file; - } - - Dwarf_Die find_die(dwarf_fileobject& fobj, Dwarf_Addr addr) - { - // Let's get to work! First see if we have a debug_aranges section so - // we can speed up the search - - Dwarf_Debug dwarf = fobj.dwarf_handle.get(); - Dwarf_Error error = DW_DLE_NE; - Dwarf_Arange* aranges; - Dwarf_Signed arange_count; - - Dwarf_Die returnDie; - bool found = false; - if (dwarf_get_aranges(dwarf, &aranges, &arange_count, &error) != DW_DLV_OK) { - aranges = NULL; - } - - if (aranges) { - // We have aranges. Get the one where our address is. - Dwarf_Arange arange; - if (dwarf_get_arange(aranges, arange_count, addr, &arange, &error) == DW_DLV_OK) { - - // We found our address. Get the compilation-unit DIE offset - // represented by the given address range. - Dwarf_Off cu_die_offset; - if (dwarf_get_cu_die_offset(arange, &cu_die_offset, &error) == DW_DLV_OK) { - // Get the DIE at the offset returned by the aranges search. - // We set is_info to 1 to specify that the offset is from - // the .debug_info section (and not .debug_types) - int dwarf_result = dwarf_offdie_b(dwarf, cu_die_offset, 1, &returnDie, &error); - - found = dwarf_result == DW_DLV_OK; - } - dwarf_dealloc(dwarf, arange, DW_DLA_ARANGE); - } - } - - if (found) - return returnDie; // The caller is responsible for freeing the die - - // The search for aranges failed. Try to find our address by scanning - // all compilation units. - Dwarf_Unsigned next_cu_header; - Dwarf_Half tag = 0; - returnDie = 0; - - while (!found && - dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, &next_cu_header, 0, &error) == DW_DLV_OK) { - - if (returnDie) - dwarf_dealloc(dwarf, returnDie, DW_DLA_DIE); - - if (dwarf_siblingof(dwarf, 0, &returnDie, &error) == DW_DLV_OK) { - if ((dwarf_tag(returnDie, &tag, &error) == DW_DLV_OK) && tag == DW_TAG_compile_unit) { - if (die_has_pc(fobj, returnDie, addr)) { - found = true; - } - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Libdwarf's next_cu_header API - // keeps its own iterator per Dwarf_Debug that can't be reset. - // We need to keep fetching elements until the end. - } - } - - if (found) - return returnDie; - - // We couldn't find any compilation units with ranges or a high/low pc. - // Try again by looking at all DIEs in all compilation units. - Dwarf_Die cudie; - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, &next_cu_header, 0, &error) == DW_DLV_OK) { - if (dwarf_siblingof(dwarf, 0, &cudie, &error) == DW_DLV_OK) { - Dwarf_Die die_mem = 0; - Dwarf_Die resultDie = find_fundie_by_pc(fobj, cudie, addr, die_mem); - - if (resultDie) { - found = true; - break; - } - } - } - - if (found) { - while (dwarf_next_cu_header_d(dwarf, 1, 0, 0, 0, 0, 0, 0, 0, 0, &next_cu_header, 0, &error) == DW_DLV_OK) { - // Reset the cu header state. Libdwarf's next_cu_header API - // keeps its own iterator per Dwarf_Debug that can't be reset. - // We need to keep fetching elements until the end. - } - } - - if (found) - return cudie; - - // We failed. - return NULL; - } -}; -# endif // BACKWARD_HAS_DWARF == 1 - -template <> -class TraceResolverImpl : public TraceResolverLinuxImpl { -}; - -#endif // BACKWARD_SYSTEM_LINUX - -#ifdef BACKWARD_SYSTEM_DARWIN - -template -class TraceResolverDarwinImpl; - -template <> -class TraceResolverDarwinImpl : public TraceResolverImplBase { -public: - template - void load_stacktrace(ST& st) - { - using namespace details; - if (st.size() == 0) { - return; - } - _symbols.reset(backtrace_symbols(st.begin(), st.size())); - } - - ResolvedTrace resolve(ResolvedTrace trace) - { - // parse: - // + - char* filename = _symbols[trace.idx]; - - // skip " " - while (*filename && *filename != ' ') - filename++; - while (*filename == ' ') - filename++; - - // find start of from end ( may contain a space) - char* p = filename + strlen(filename) - 1; - // skip to start of " + " - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - char* funcname_end = p + 1; - - // skip to start of "" - while (p > filename && *p != ' ') - p--; - char* funcname = p + 1; - - // skip to start of " " - while (p > filename && *p == ' ') - p--; - while (p > filename && *p != ' ') - p--; - while (p > filename && *p == ' ') - p--; - - // skip "", handling the case where it contains a - char* filename_end = p + 1; - if (p == filename) { - // something went wrong, give up - filename_end = filename + strlen(filename); - funcname = filename_end; - } - trace.object_filename.assign(filename, filename_end); // ok even if filename_end is the ending \0 - // (then we assign entire string) - - if (*funcname) { // if it's not end of string - *funcname_end = '\0'; - - trace.object_function = this->demangle(funcname); - trace.object_function += " "; - trace.object_function += (funcname_end + 1); - trace.source.function = trace.object_function; // we cannot do better. - } - return trace; - } - -private: - details::handle _symbols; -}; - -template <> -class TraceResolverImpl : public TraceResolverDarwinImpl { -}; - -#endif // BACKWARD_SYSTEM_DARWIN - -#ifdef BACKWARD_SYSTEM_WINDOWS - -// Load all symbol info -// Based on: -// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 - -struct module_data { - std::string image_name; - std::string module_name; - void* base_address; - DWORD load_size; -}; - -class get_mod_info { - HANDLE process; - static const int buffer_length = 4096; - -public: - get_mod_info(HANDLE h) : process(h) {} - - module_data operator()(HMODULE module) - { - module_data ret; - char temp[buffer_length]; - MODULEINFO mi; - - GetModuleInformation(process, module, &mi, sizeof(mi)); - ret.base_address = mi.lpBaseOfDll; - ret.load_size = mi.SizeOfImage; - - GetModuleFileNameEx(process, module, temp, sizeof(temp)); - ret.image_name = temp; - GetModuleBaseName(process, module, temp, sizeof(temp)); - ret.module_name = temp; - std::vector img(ret.image_name.begin(), ret.image_name.end()); - std::vector mod(ret.module_name.begin(), ret.module_name.end()); - SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size); - return ret; - } -}; - -template <> -class TraceResolverImpl { -public: - TraceResolverImpl() - { - - HANDLE process = GetCurrentProcess(); - - std::vector modules; - DWORD cbNeeded; - std::vector module_handles(1); - SymInitialize(process, NULL, false); - DWORD symOptions = SymGetOptions(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - SymSetOptions(symOptions); - EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); - module_handles.resize(cbNeeded / sizeof(HMODULE)); - EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); - std::transform( - module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process)); - void* base = modules[0].base_address; - IMAGE_NT_HEADERS* h = ImageNtHeader(base); - image_type = h->FileHeader.Machine; - } - - template - void load_stacktrace(ST&) - { - } - - static const int max_sym_len = 255; - struct symbol_t { - SYMBOL_INFO sym; - char buffer[max_sym_len]; - } sym; - - DWORD64 displacement; - - ResolvedTrace resolve(ResolvedTrace t) - { - HANDLE process = GetCurrentProcess(); - - char name[256]; - - memset(&sym, 0, sizeof sym); - sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); - sym.sym.MaxNameLen = max_sym_len; - - if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { - // TODO: error handling everywhere - LPTSTR lpMsgBuf; - DWORD dw = GetLastError(); - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&lpMsgBuf, - 0, - NULL); - - printf(lpMsgBuf); - - // abort(); - } - UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE); - - DWORD offset = 0; - IMAGEHLP_LINE line; - if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) { - t.object_filename = line.FileName; - t.source.filename = line.FileName; - t.source.line = line.LineNumber; - t.source.col = offset; - } - - t.source.function = name; - t.object_filename = ""; - t.object_function = name; - - return t; - } - - DWORD machine_type() const { return image_type; } - -private: - DWORD image_type; -}; - -#endif - -class TraceResolver : public TraceResolverImpl { -}; - -/*************** CODE SNIPPET ***************/ - -class SourceFile { -public: - typedef std::vector> lines_t; - - SourceFile() {} - SourceFile(const std::string& path) - { - // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains - // a colon-separated list of path prefixes. Try prepending each - // to the given path until a valid file is found. - const std::vector& prefixes = get_paths_from_env_variable(); - for (size_t i = 0; i < prefixes.size(); ++i) { - // Double slashes (//) should not be a problem. - std::string new_path = prefixes[i] + '/' + path; - _file.reset(new std::ifstream(new_path.c_str())); - if (is_open()) - break; - } - // 2. If no valid file found then fallback to opening the path as-is. - if (!_file || !is_open()) { - _file.reset(new std::ifstream(path.c_str())); - } - } - bool is_open() const { return _file->is_open(); } - - lines_t& get_lines(unsigned line_start, unsigned line_count, lines_t& lines) - { - using namespace std; - // This function make uses of the dumbest algo ever: - // 1) seek(0) - // 2) read lines one by one and discard until line_start - // 3) read line one by one until line_start + line_count - // - // If you are getting snippets many time from the same file, it is - // somewhat a waste of CPU, feel free to benchmark and propose a - // better solution ;) - - _file->clear(); - _file->seekg(0); - string line; - unsigned line_idx; - - for (line_idx = 1; line_idx < line_start; ++line_idx) { - std::getline(*_file, line); - if (!*_file) { - return lines; - } - } - - // think of it like a lambda in C++98 ;) - // but look, I will reuse it two times! - // What a good boy am I. - struct isspace { - bool operator()(char c) { return std::isspace(c); } - }; - - bool started = false; - for (; line_idx < line_start + line_count; ++line_idx) { - getline(*_file, line); - if (!*_file) { - return lines; - } - if (!started) { - if (std::find_if(line.begin(), line.end(), not_isspace()) == line.end()) - continue; - started = true; - } - lines.push_back(make_pair(line_idx, line)); - } - - lines.erase(std::find_if(lines.rbegin(), lines.rend(), not_isempty()).base(), lines.end()); - return lines; - } - - lines_t get_lines(unsigned line_start, unsigned line_count) - { - lines_t lines; - return get_lines(line_start, line_count, lines); - } - - // there is no find_if_not in C++98, lets do something crappy to - // workaround. - struct not_isspace { - bool operator()(char c) { return !std::isspace(c); } - }; - // and define this one here because C++98 is not happy with local defined - // struct passed to template functions, fuuuu. - struct not_isempty { - bool operator()(const lines_t::value_type& p) - { - return !(std::find_if(p.second.begin(), p.second.end(), not_isspace()) == p.second.end()); - } - }; - - void swap(SourceFile& b) { _file.swap(b._file); } - -#ifdef BACKWARD_ATLEAST_CXX11 - SourceFile(SourceFile&& from) : _file(nullptr) { swap(from); } - SourceFile& operator=(SourceFile&& from) - { - swap(from); - return *this; - } -#else - explicit SourceFile(const SourceFile& from) - { - // some sort of poor man's move semantic. - swap(const_cast(from)); - } - SourceFile& operator=(const SourceFile& from) - { - // some sort of poor man's move semantic. - swap(const_cast(from)); - return *this; - } -#endif - -private: - details::handle> _file; - - std::vector get_paths_from_env_variable_impl() - { - std::vector paths; - const char* prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES"); - if (prefixes_str && prefixes_str[0]) { - paths = details::split_source_prefixes(prefixes_str); - } - return paths; - } - - const std::vector& get_paths_from_env_variable() - { - static std::vector paths = get_paths_from_env_variable_impl(); - return paths; - } - -#ifdef BACKWARD_ATLEAST_CXX11 - SourceFile(const SourceFile&) = delete; - SourceFile& operator=(const SourceFile&) = delete; -#endif -}; - -class SnippetFactory { -public: - typedef SourceFile::lines_t lines_t; - - lines_t get_snippet(const std::string& filename, unsigned line_start, unsigned context_size) - { - - SourceFile& src_file = get_src_file(filename); - unsigned start = line_start - context_size / 2; - return src_file.get_lines(start, context_size); - } - - lines_t get_combined_snippet( - const std::string& filename_a, - unsigned line_a, - const std::string& filename_b, - unsigned line_b, - unsigned context_size) - { - SourceFile& src_file_a = get_src_file(filename_a); - SourceFile& src_file_b = get_src_file(filename_b); - - lines_t lines = src_file_a.get_lines(line_a - context_size / 4, context_size / 2); - src_file_b.get_lines(line_b - context_size / 4, context_size / 2, lines); - return lines; - } - - lines_t get_coalesced_snippet(const std::string& filename, unsigned line_a, unsigned line_b, unsigned context_size) - { - SourceFile& src_file = get_src_file(filename); - - using std::max; - using std::min; - unsigned a = min(line_a, line_b); - unsigned b = max(line_a, line_b); - - if ((b - a) < (context_size / 3)) { - return src_file.get_lines((a + b - context_size + 1) / 2, context_size); - } - - lines_t lines = src_file.get_lines(a - context_size / 4, context_size / 2); - src_file.get_lines(b - context_size / 4, context_size / 2, lines); - return lines; - } - -private: - typedef details::hashtable::type src_files_t; - src_files_t _src_files; - - SourceFile& get_src_file(const std::string& filename) - { - src_files_t::iterator it = _src_files.find(filename); - if (it != _src_files.end()) { - return it->second; - } - SourceFile& new_src_file = _src_files[filename]; - new_src_file = SourceFile(filename); - return new_src_file; - } -}; - -/*************** PRINTER ***************/ - -namespace ColorMode { -enum type { automatic, never, always }; -} - -class cfile_streambuf : public std::streambuf { -public: - cfile_streambuf(FILE* _sink) : sink(_sink) {} - int_type underflow() override { return traits_type::eof(); } - int_type overflow(int_type ch) override - { - if (traits_type::not_eof(ch) && fwrite(&ch, sizeof ch, 1, sink) == 1) { - return ch; - } - return traits_type::eof(); - } - - std::streamsize xsputn(const char_type* s, std::streamsize count) override - { - return static_cast(fwrite(s, sizeof *s, static_cast(count), sink)); - } - -#ifdef BACKWARD_ATLEAST_CXX11 -public: - cfile_streambuf(const cfile_streambuf&) = delete; - cfile_streambuf& operator=(const cfile_streambuf&) = delete; -#else -private: - cfile_streambuf(const cfile_streambuf&); - cfile_streambuf& operator=(const cfile_streambuf&); -#endif - -private: - FILE* sink; - std::vector buffer; -}; - -#ifdef BACKWARD_SYSTEM_LINUX - -namespace Color { -enum type { yellow = 33, purple = 35, reset = 39 }; -} // namespace Color - -class Colorize { -public: - Colorize(std::ostream& os) : _os(os), _reset(false), _enabled(false) {} - - void activate(ColorMode::type mode) { _enabled = mode == ColorMode::always; } - - void activate(ColorMode::type mode, FILE* fp) { activate(mode, fileno(fp)); } - - void set_color(Color::type ccode) - { - if (!_enabled) - return; - - // I assume that the terminal can handle basic colors. Seriously I - // don't want to deal with all the termcap shit. - _os << "\033[" << static_cast(ccode) << "m"; - _reset = (ccode != Color::reset); - } - - ~Colorize() - { - if (_reset) { - set_color(Color::reset); - } - } - -private: - void activate(ColorMode::type mode, int fd) - { - activate(mode == ColorMode::automatic && isatty(fd) ? ColorMode::always : mode); - } - - std::ostream& _os; - bool _reset; - bool _enabled; -}; - -#else // ndef BACKWARD_SYSTEM_LINUX - -namespace Color { -enum type { yellow = 0, purple = 0, reset = 0 }; -} // namespace Color - -class Colorize { -public: - Colorize(std::ostream&) {} - void activate(ColorMode::type) {} - void activate(ColorMode::type, FILE*) {} - void set_color(Color::type) {} -}; - -#endif // BACKWARD_SYSTEM_LINUX - -class Printer { -public: - bool snippet; - ColorMode::type color_mode; - bool address; - bool object; - int inliner_context_size; - int trace_context_size; - - Printer() - : snippet(true), color_mode(ColorMode::automatic), address(false), object(false), inliner_context_size(5), - trace_context_size(7) - { - } - - template - FILE* print(ST& st, FILE* fp = stderr) - { - cfile_streambuf obuf(fp); - std::ostream os(&obuf); - Colorize colorize(os); - colorize.activate(color_mode, fp); - print_stacktrace(st, os, colorize); - return fp; - } - - template - std::ostream& print(ST& st, std::ostream& os) - { - Colorize colorize(os); - colorize.activate(color_mode); - print_stacktrace(st, os, colorize); - return os; - } - - template - FILE* print(IT begin, IT end, FILE* fp = stderr, size_t thread_id = 0) - { - cfile_streambuf obuf(fp); - std::ostream os(&obuf); - Colorize colorize(os); - colorize.activate(color_mode, fp); - print_stacktrace(begin, end, os, thread_id, colorize); - return fp; - } - - template - std::ostream& print(IT begin, IT end, std::ostream& os, size_t thread_id = 0) - { - Colorize colorize(os); - colorize.activate(color_mode); - print_stacktrace(begin, end, os, thread_id, colorize); - return os; - } - - TraceResolver const& resolver() const { return _resolver; } - -private: - TraceResolver _resolver; - SnippetFactory _snippets; - - template - void print_stacktrace(ST& st, std::ostream& os, Colorize& colorize) - { - print_header(os, st.thread_id()); - _resolver.load_stacktrace(st); - for (size_t trace_idx = st.size(); trace_idx > 0; --trace_idx) { - print_trace(os, _resolver.resolve(st[trace_idx - 1]), colorize); - } - } - - template - void print_stacktrace(IT begin, IT end, std::ostream& os, size_t thread_id, Colorize& colorize) - { - print_header(os, thread_id); - for (; begin != end; ++begin) { - print_trace(os, *begin, colorize); - } - } - - void print_header(std::ostream& os, size_t thread_id) - { - os << "Stack trace (most recent call last)"; - if (thread_id) { - os << " in thread " << thread_id; - } - os << ":\n"; - } - - void print_trace(std::ostream& os, const ResolvedTrace& trace, Colorize& colorize) - { - os << "#" << std::left << std::setw(2) << trace.idx << std::right; - bool already_indented = true; - - if (!trace.source.filename.size() || object) { - os << " Object \"" << trace.object_filename << "\", at " << trace.addr << ", in " << trace.object_function - << "\n"; - already_indented = false; - } - - for (size_t inliner_idx = trace.inliners.size(); inliner_idx > 0; --inliner_idx) { - if (!already_indented) { - os << " "; - } - const ResolvedTrace::SourceLoc& inliner_loc = trace.inliners[inliner_idx - 1]; - print_source_loc(os, " | ", inliner_loc); - if (snippet) { - print_snippet(os, " | ", inliner_loc, colorize, Color::purple, inliner_context_size); - } - already_indented = false; - } - - if (trace.source.filename.size()) { - if (!already_indented) { - os << " "; - } - print_source_loc(os, " ", trace.source, trace.addr); - if (snippet) { - print_snippet(os, " ", trace.source, colorize, Color::yellow, trace_context_size); - } - } - } - - void print_snippet( - std::ostream& os, - const char* indent, - const ResolvedTrace::SourceLoc& source_loc, - Colorize& colorize, - Color::type color_code, - int context_size) - { - using namespace std; - typedef SnippetFactory::lines_t lines_t; - - lines_t lines = - _snippets.get_snippet(source_loc.filename, source_loc.line, static_cast(context_size)); - - for (lines_t::const_iterator it = lines.begin(); it != lines.end(); ++it) { - if (it->first == source_loc.line) { - colorize.set_color(color_code); - os << indent << ">"; - } - else { - os << indent << " "; - } - os << std::setw(4) << it->first << ": " << it->second << "\n"; - if (it->first == source_loc.line) { - colorize.set_color(Color::reset); - } - } - } - - void print_source_loc( - std::ostream& os, - const char* indent, - const ResolvedTrace::SourceLoc& source_loc, - void* addr = nullptr) - { - os << indent << "Source \"" << source_loc.filename << "\", line " << source_loc.line << ", in " - << source_loc.function; - - if (address && addr != nullptr) { - os << " [" << addr << "]"; - } - os << "\n"; - } -}; - -/*************** SIGNALS HANDLING ***************/ - -#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) - -class SignalHandling { -public: - static std::vector make_default_signals() - { - const int posix_signals[] = { - // Signals for which the default action is "Core". - SIGABRT, // Abort signal from abort(3) - SIGBUS, // Bus error (bad memory access) - SIGFPE, // Floating point exception - SIGILL, // Illegal Instruction - SIGIOT, // IOT trap. A synonym for SIGABRT - SIGQUIT, // Quit from keyboard - SIGSEGV, // Invalid memory reference - SIGSYS, // Bad argument to routine (SVr4) - SIGTRAP, // Trace/breakpoint trap - SIGXCPU, // CPU time limit exceeded (4.2BSD) - SIGXFSZ, // File size limit exceeded (4.2BSD) -# if defined(BACKWARD_SYSTEM_DARWIN) - SIGEMT, // emulation instruction executed -# endif - }; - return std::vector(posix_signals, posix_signals + sizeof posix_signals / sizeof posix_signals[0]); - } - - SignalHandling(const std::vector& posix_signals = make_default_signals()) : _loaded(false) - { - bool success = true; - - const size_t stack_size = 1024 * 1024 * 8; - _stack_content.reset(static_cast(malloc(stack_size))); - if (_stack_content) { - stack_t ss; - ss.ss_sp = _stack_content.get(); - ss.ss_size = stack_size; - ss.ss_flags = 0; - if (sigaltstack(&ss, nullptr) < 0) { - success = false; - } - } - else { - success = false; - } - - for (size_t i = 0; i < posix_signals.size(); ++i) { - struct sigaction action; - memset(&action, 0, sizeof action); - action.sa_flags = static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); - sigfillset(&action.sa_mask); - sigdelset(&action.sa_mask, posix_signals[i]); -# if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -# endif - action.sa_sigaction = &sig_handler; -# if defined(__clang__) -# pragma clang diagnostic pop -# endif - - int r = sigaction(posix_signals[i], &action, nullptr); - if (r < 0) - success = false; - } - - _loaded = success; - } - - bool loaded() const { return _loaded; } - - static void handleSignal(int, siginfo_t* info, void* _ctx) - { - ucontext_t* uctx = static_cast(_ctx); - - StackTrace st; - void* error_addr = nullptr; -# ifdef REG_RIP // x86_64 - error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); -# elif defined(REG_EIP) // x86_32 - error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); -# elif defined(__arm__) - error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); -# elif defined(__aarch64__) - error_addr = reinterpret_cast(uctx->uc_mcontext.pc); -# elif defined(__mips__) - error_addr = reinterpret_cast(reinterpret_cast(&uctx->uc_mcontext)->sc_pc); -# elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) - error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); -# elif defined(__s390x__) - error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); -# elif defined(__APPLE__) && defined(__x86_64__) - error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); -# elif defined(__APPLE__) - error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); -# else -# warning ":/ sorry, ain't know no nothing none not of your architecture!" -# endif - if (error_addr) { - st.load_from(error_addr, 32); - } - else { - st.load_here(32); - } - - Printer printer; - printer.address = true; - printer.print(st, stderr); - -# if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L - psiginfo(info, nullptr); -# else - (void)info; -# endif - } - -private: - details::handle _stack_content; - bool _loaded; - -# ifdef __GNUC__ - __attribute__((noreturn)) -# endif - static void - sig_handler(int signo, siginfo_t* info, void* _ctx) - { - handleSignal(signo, info, _ctx); - - // try to forward the signal. - raise(info->si_signo); - - // terminate the process immediately. - puts("watf? exit"); - _exit(EXIT_FAILURE); - } -}; - -#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN - -#ifdef BACKWARD_SYSTEM_WINDOWS - -class SignalHandling { -public: - SignalHandling(const std::vector& = std::vector()) - : reporter_thread_([]() { - /* We handle crashes in a utility thread: - backward structures and some Windows functions called here - need stack space, which we do not have when we encounter a - stack overflow. - To support reporting stack traces during a stack overflow, - we create a utility thread at startup, which waits until a - crash happens or the program exits normally. */ - - { - std::unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::running; }); - } - if (crashed() == crash_status::crashed) { - handle_stacktrace(skip_recs()); - } - { - std::unique_lock lk(mtx()); - crashed() = crash_status::ending; - } - cv().notify_one(); - }) - { - SetUnhandledExceptionFilter(crash_handler); - - signal(SIGABRT, signal_handler); - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); - - set_terminate(&terminator); - set_unexpected(&terminator); - _set_purecall_handler(&terminator); - _set_invalid_parameter_handler(&invalid_parameter_handler); - } - bool loaded() const { return true; } - - ~SignalHandling() - { - { - std::unique_lock lk(mtx()); - crashed() = crash_status::normal_exit; - } - - cv().notify_one(); - - reporter_thread_.join(); - } - -private: - static CONTEXT* ctx() - { - static CONTEXT data; - return &data; - } - - enum class crash_status { running, crashed, normal_exit, ending }; - - static crash_status& crashed() - { - static crash_status data; - return data; - } - - static std::mutex& mtx() - { - static std::mutex data; - return data; - } - - static std::condition_variable& cv() - { - static std::condition_variable data; - return data; - } - - static HANDLE& thread_handle() - { - static HANDLE handle; - return handle; - } - - std::thread reporter_thread_; - - // TODO: how not to hardcode these? - static const constexpr int signal_skip_recs = -# ifdef __clang__ - // With clang, RtlCaptureContext also captures the stack frame of the - // current function Below that, there ar 3 internal Windows functions - 4 -# else - // With MSVC cl, RtlCaptureContext misses the stack frame of the current - // function The first entries during StackWalk are the 3 internal Windows - // functions - 3 -# endif - ; - - static int& skip_recs() - { - static int data; - return data; - } - - static inline void terminator() - { - crash_handler(signal_skip_recs); - abort(); - } - - static inline void signal_handler(int) - { - crash_handler(signal_skip_recs); - abort(); - } - - static inline void __cdecl invalid_parameter_handler( - const wchar_t*, - const wchar_t*, - const wchar_t*, - unsigned int, - uintptr_t) - { - crash_handler(signal_skip_recs); - abort(); - } - - NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) - { - // The exception info supplies a trace from exactly where the issue was, - // no need to skip records - crash_handler(0, info->ContextRecord); - return EXCEPTION_CONTINUE_SEARCH; - } - - NOINLINE static void crash_handler(int skip, CONTEXT* ct = nullptr) - { - - if (ct == nullptr) { - RtlCaptureContext(ctx()); - } - else { - memcpy(ctx(), ct, sizeof(CONTEXT)); - } - DuplicateHandle( - GetCurrentProcess(), - GetCurrentThread(), - GetCurrentProcess(), - &thread_handle(), - 0, - FALSE, - DUPLICATE_SAME_ACCESS); - - skip_recs() = skip; - - { - std::unique_lock lk(mtx()); - crashed() = crash_status::crashed; - } - - cv().notify_one(); - - { - std::unique_lock lk(mtx()); - cv().wait(lk, [] { return crashed() != crash_status::crashed; }); - } - } - - static void handle_stacktrace(int skip_frames = 0) - { - // printer creates the TraceResolver, which can supply us a machine type - // for stack walking. Without this, StackTrace can only guess using some - // macros. - // StackTrace also requires that the PDBs are already loaded, which is done - // in the constructor of TraceResolver - Printer printer; - - StackTrace st; - st.set_machine_type(printer.resolver().machine_type()); - st.set_context(ctx()); - st.set_thread_handle(thread_handle()); - st.load_here(32 + skip_frames); - st.skip_n_firsts(skip_frames); - - printer.address = true; - printer.print(st, std::cerr); - } -}; - -#endif // BACKWARD_SYSTEM_WINDOWS - -#ifdef BACKWARD_SYSTEM_UNKNOWN - -class SignalHandling { -public: - SignalHandling(const std::vector& = std::vector()) {} - bool init() { return false; } - bool loaded() { return false; } -}; - -#endif // BACKWARD_SYSTEM_UNKNOWN - -} // namespace backward - -#endif /* H_GUARD */ -// clang-format on diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/logging.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/logging.cpp deleted file mode 100644 index e68aafa24..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/logging.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -#include "aws/logging/logging.h" -#include -#include -#include - -#define LAMBDA_RUNTIME_API __attribute__((visibility("default"))) - -namespace aws { -namespace logging { - -static inline char const* get_prefix(verbosity v) -{ - switch (v) { - case verbosity::error: - return "[ERROR]"; - case verbosity::info: - return "[INFO]"; - case verbosity::debug: - return "[DEBUG]"; - default: - return "[UNKNOWN]"; - } -} - -LAMBDA_RUNTIME_API -void log(verbosity v, char const* tag, char const* msg, va_list args) -{ - va_list copy; - va_copy(copy, args); - const int sz = vsnprintf(nullptr, 0, msg, args) + 1; - if (sz < 0) { - puts("error occurred during log formatting!\n"); - va_end(copy); - return; - } - constexpr int max_stack_buffer_size = 512; - std::array buf; - char* out = buf.data(); - if (sz >= max_stack_buffer_size) { - out = new char[sz]; - } - - vsnprintf(out, sz, msg, copy); - va_end(copy); - auto ms = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()); - printf("%s [%lld] %s %s\n", get_prefix(v), static_cast(ms.count()), tag, out); - // stdout is not line-buffered when redirected (for example to a file or to another process) so we must flush it - // manually. - fflush(stdout); - if (out != buf.data()) { - delete[] out; - } -} -} // namespace logging -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp deleted file mode 100644 index 2d86a1d7e..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/runtime.cpp +++ /dev/null @@ -1,545 +0,0 @@ -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include "aws/lambda-runtime/runtime.h" -#include "aws/lambda-runtime/version.h" -#include "aws/lambda-runtime/outcome.h" -#include "aws/logging/logging.h" -#include "aws/http/response.h" - -#include -#include -#include // for ULONG_MAX -#include -#include -#include -#include // for strtoul -#include - -#define AWS_LAMBDA_RUNTIME_API __attribute__((visibility("default"))) - -namespace aws { -namespace lambda_runtime { - -static constexpr auto LOG_TAG = "LAMBDA_RUNTIME"; -static constexpr auto REQUEST_ID_HEADER = "lambda-runtime-aws-request-id"; -static constexpr auto TRACE_ID_HEADER = "lambda-runtime-trace-id"; -static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; -static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; -static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; -static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; -static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; -static constexpr auto INVOCATION_ID_HEADER = "lambda-runtime-invocation-id"; -thread_local static CURL* m_curl_handle = curl_easy_init(); - -enum Endpoints { - INIT, - NEXT, - RESULT, -}; - -static bool is_success(aws::http::response_code httpcode) -{ - constexpr auto http_first_success_error_code = 200; - constexpr auto http_last_success_error_code = 299; - auto const code = static_cast(httpcode); - return code >= http_first_success_error_code && code <= http_last_success_error_code; -} - -static size_t write_data(char* ptr, size_t size, size_t nmemb, void* userdata) -{ - if (!ptr) { - return 0; - } - - auto const resp = static_cast(userdata); - assert(size == 1); - (void)size; // avoid warning in release builds - assert(resp); - resp->append_body(ptr, nmemb); - return nmemb; -} - -// std::isspace has a few edge cases that would trigger UB. In particular, the documentation says: -// "The behavior is undefined if the value of the input is not representable as unsigned char and is not equal to EOF." -// So, this function does the simple obvious thing instead. -static inline bool is_whitespace(int ch) -{ - constexpr int space = 0x20; // space (0x20, ' ') - constexpr int form_feed = 0x0c; // form feed (0x0c, '\f') - constexpr int line_feed = 0x0a; // line feed (0x0a, '\n') - constexpr int carriage_return = 0x0d; // carriage return (0x0d, '\r') - constexpr int horizontal_tab = 0x09; // horizontal tab (0x09, '\t') - constexpr int vertical_tab = 0x0b; // vertical tab (0x0b, '\v') - switch (ch) { - case space: - case form_feed: - case line_feed: - case carriage_return: - case horizontal_tab: - case vertical_tab: - return true; - default: - return false; - } -} - -static inline std::string trim(std::string s) -{ - // trim right - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !is_whitespace(ch); }).base(), s.end()); - // trim left - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !is_whitespace(ch); })); - return s; -} - -static size_t write_header(char* ptr, size_t size, size_t nmemb, void* userdata) -{ - if (!ptr) { - return 0; - } - - logging::log_debug(LOG_TAG, "received header: %s", std::string(ptr, nmemb).c_str()); - - auto const resp = static_cast(userdata); - assert(resp); - for (size_t i = 0; i < nmemb; i++) { - if (ptr[i] != ':') { - continue; - } - std::string key{ptr, i}; - std::string value{ptr + i + 1, nmemb - i - 1}; - resp->add_header(trim(key), trim(value)); - break; - } - return size * nmemb; -} - -static size_t read_data(char* buffer, size_t size, size_t nitems, void* userdata) -{ - auto const limit = size * nitems; - auto ctx = static_cast*>(userdata); - assert(ctx); - auto const unread = ctx->first.length() - ctx->second; - if (0 == unread) { - return 0; - } - - if (unread <= limit) { - std::copy_n(ctx->first.begin() + ctx->second, unread, buffer); - ctx->second += unread; - return unread; - } - - std::copy_n(ctx->first.begin() + ctx->second, limit, buffer); - ctx->second += limit; - return limit; -} - -#ifndef NDEBUG -static int rt_curl_debug_callback(CURL* handle, curl_infotype type, char* data, size_t size, void* userdata) -{ - (void)handle; - (void)type; - (void)userdata; - std::string s(data, size); - logging::log_debug(LOG_TAG, "CURL DBG: %s", s.c_str()); - return 0; -} -#endif - -runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cpp/" + std::string(get_version())) {} - -runtime::runtime(std::string const& endpoint, std::string const& user_agent) - : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", - endpoint + "/2018-06-01/runtime/invocation/next", - endpoint + "/2018-06-01/runtime/invocation/"}} -{ - if (!lambda_runtime::m_curl_handle) { - logging::log_error(LOG_TAG, "Failed to acquire curl easy handle for next."); - } -} - -runtime::~runtime() -{ - curl_easy_cleanup(lambda_runtime::m_curl_handle); -} - -void runtime::set_curl_next_options() -{ - // lambda freezes the container when no further tasks are available. The freezing period could be longer than the - // request timeout, which causes the following get_next request to fail with a timeout error. - curl_easy_reset(lambda_runtime::m_curl_handle); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, m_endpoints[Endpoints::NEXT].c_str()); - - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); - -#ifndef NDEBUG - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); -#endif -} - -void runtime::set_curl_post_result_options() -{ - curl_easy_reset(lambda_runtime::m_curl_handle); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TIMEOUT, 0L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_CONNECTTIMEOUT, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_TCP_NODELAY, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_POST, 1L); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READFUNCTION, read_data); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); - - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_PROXY, ""); - -#ifndef NDEBUG - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); -#endif -} - -runtime::next_outcome runtime::get_next() -{ - http::response resp; - set_curl_next_options(); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); - - curl_slist* headers = nullptr; - headers = curl_slist_append(headers, m_user_agent_header.c_str()); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); - - logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); - CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); - logging::log_debug(LOG_TAG, "Completed request to %s", m_endpoints[Endpoints::NEXT].c_str()); - curl_slist_free_all(headers); - - if (curl_code != CURLE_OK) { - logging::log_debug(LOG_TAG, "CURL returned error code %d - %s", curl_code, curl_easy_strerror(curl_code)); - logging::log_error(LOG_TAG, "Failed to get next invocation. No Response from endpoint"); - return aws::http::response_code::REQUEST_NOT_MADE; - } - - { - long resp_code; - curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &resp_code); - resp.set_response_code(static_cast(resp_code)); - } - - { - char* content_type = nullptr; - curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_CONTENT_TYPE, &content_type); - resp.set_content_type(content_type); - } - - if (!is_success(resp.get_response_code())) { - logging::log_error( - LOG_TAG, - "Failed to get next invocation. Http Response code: %d", - static_cast(resp.get_response_code())); - return resp.get_response_code(); - } - - if (!resp.has_header(REQUEST_ID_HEADER)) { - logging::log_error(LOG_TAG, "Failed to find header %s in response", REQUEST_ID_HEADER); - return aws::http::response_code::REQUEST_NOT_MADE; - } - invocation_request req; - req.payload = resp.get_body(); - req.request_id = resp.get_header(REQUEST_ID_HEADER); - - if (resp.has_header(TRACE_ID_HEADER)) { - req.xray_trace_id = resp.get_header(TRACE_ID_HEADER); - } - - if (resp.has_header(CLIENT_CONTEXT_HEADER)) { - req.client_context = resp.get_header(CLIENT_CONTEXT_HEADER); - } - - if (resp.has_header(COGNITO_IDENTITY_HEADER)) { - req.cognito_identity = resp.get_header(COGNITO_IDENTITY_HEADER); - } - - if (resp.has_header(FUNCTION_ARN_HEADER)) { - req.function_arn = resp.get_header(FUNCTION_ARN_HEADER); - } - - if (resp.has_header(DEADLINE_MS_HEADER)) { - auto const& deadline_string = resp.get_header(DEADLINE_MS_HEADER); - constexpr int base = 10; - unsigned long ms = strtoul(deadline_string.c_str(), nullptr, base); - assert(ms > 0); - assert(ms < ULONG_MAX); - req.deadline += std::chrono::milliseconds(ms); - logging::log_info( - LOG_TAG, - "Received payload: %s\nTime remaining: %" PRId64, - req.payload.c_str(), - static_cast(req.get_time_remaining().count())); - } - - if (resp.has_header(TENANT_ID_HEADER)) { - req.tenant_id = resp.get_header(TENANT_ID_HEADER); - } - - if (resp.has_header(INVOCATION_ID_HEADER)) { - req.invocation_id = resp.get_header(INVOCATION_ID_HEADER); - } - return next_outcome(req); -} - -runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id) -{ - std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response"; - return do_post(url, request_id, handler_response, invocation_id); -} - -runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) -{ - return post_success(request_id, handler_response, ""); -} - -runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response, std::string const& invocation_id) -{ - std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error"; - return do_post(url, request_id, handler_response, invocation_id); -} - -runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response) -{ - return post_failure(request_id, handler_response, ""); -} - -runtime::post_outcome runtime::do_post( - std::string const& url, - std::string const& request_id, - invocation_response const& handler_response, - std::string const& invocation_id) -{ - set_curl_post_result_options(); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); - logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); - - curl_slist* headers = nullptr; - if (handler_response.get_content_type().empty()) { - headers = curl_slist_append(headers, "content-type: text/html"); - } - else { - headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str()); - } - - headers = curl_slist_append(headers, "Expect:"); - headers = curl_slist_append(headers, "transfer-encoding:"); - headers = curl_slist_append(headers, m_user_agent_header.c_str()); - if (!invocation_id.empty()) { - headers = curl_slist_append(headers, (std::string(INVOCATION_ID_HEADER) + ": " + invocation_id).c_str()); - } - auto const& payload = handler_response.get_payload(); - logging::log_debug( - LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); - headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str()); - - std::pair ctx{payload, 0}; - aws::http::response resp; - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_WRITEDATA, &resp); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HEADERDATA, &resp); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_READDATA, &ctx); - curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_HTTPHEADER, headers); - CURLcode curl_code = curl_easy_perform(lambda_runtime::m_curl_handle); - curl_slist_free_all(headers); - - if (curl_code != CURLE_OK) { - logging::log_debug( - LOG_TAG, - "CURL returned error code %d - %s, for invocation %s", - curl_code, - curl_easy_strerror(curl_code), - request_id.c_str()); - return aws::http::response_code::REQUEST_NOT_MADE; - } - - long http_response_code; - curl_easy_getinfo(lambda_runtime::m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code); - - if (!is_success(aws::http::response_code(http_response_code))) { - logging::log_error( - LOG_TAG, "Failed to post handler success response. Http response code: %ld. %s", http_response_code, resp.get_body().c_str()); - return aws::http::response_code(http_response_code); - } - - return post_outcome(no_result{}); -} - -static bool handle_post_outcome(runtime::post_outcome const& o, std::string const& request_id) -{ - if (o.is_success()) { - return true; - } - - if (o.get_failure() == aws::http::response_code::REQUEST_NOT_MADE) { - logging::log_error(LOG_TAG, "Failed to send HTTP request for invocation %s.", request_id.c_str()); - return false; - } - - logging::log_info( - LOG_TAG, - "HTTP Request for invocation %s was not successful. HTTP response code: %d.", - request_id.c_str(), - static_cast(o.get_failure())); - return false; -} - -AWS_LAMBDA_RUNTIME_API -void run_handler(std::function const& handler) -{ - logging::log_info(LOG_TAG, "Initializing the C++ Lambda Runtime version %s", aws::lambda_runtime::get_version()); - std::string endpoint("http://"); - if (auto ep = std::getenv("AWS_LAMBDA_RUNTIME_API")) { - assert(ep); - logging::log_debug(LOG_TAG, "LAMBDA_SERVER_ADDRESS defined in environment as: %s", ep); - endpoint += ep; - } - - runtime rt(endpoint); - - size_t retries = 0; - size_t const max_retries = 3; - - while (retries < max_retries) { - auto next_outcome = rt.get_next(); - if (!next_outcome.is_success()) { - if (next_outcome.get_failure() == aws::http::response_code::REQUEST_NOT_MADE) { - ++retries; - continue; - } - - logging::log_info( - LOG_TAG, - "HTTP request was not successful. HTTP response code: %d. Retrying..", - static_cast(next_outcome.get_failure())); - ++retries; - continue; - } - - retries = 0; - - auto const req = std::move(next_outcome).get_result(); - logging::log_info(LOG_TAG, "Invoking user handler"); - invocation_response res = handler(req); - logging::log_info(LOG_TAG, "Invoking user handler completed."); - - if (res.is_success()) { - const auto post_outcome = rt.post_success(req.request_id, res, req.invocation_id); - if (!handle_post_outcome(post_outcome, req.request_id)) { - return; // TODO: implement a better retry strategy - } - } - else { - const auto post_outcome = rt.post_failure(req.request_id, res, req.invocation_id); - if (!handle_post_outcome(post_outcome, req.request_id)) { - return; // TODO: implement a better retry strategy - } - } - } - - if (retries == max_retries) { - logging::log_error( - LOG_TAG, "Exhausted all retries. This is probably a bug in libcurl v" LIBCURL_VERSION " Exiting!"); - } -} - -static std::string json_escape(std::string const& in) -{ - constexpr char last_non_printable_character = 31; - std::string out; - out.reserve(in.length()); // most strings will end up identical - for (char ch : in) { - if (ch > last_non_printable_character && ch != '\"' && ch != '\\') { - out.append(1, ch); - } - else { - out.append(1, '\\'); - switch (ch) { - case '\\': - out.append(1, '\\'); - break; - case '"': - out.append(1, '"'); - break; - case '\b': - out.append(1, 'b'); - break; - case '\f': - out.append(1, 'f'); - break; - case '\n': - out.append(1, 'n'); - break; - case '\r': - out.append(1, 'r'); - break; - case '\t': - out.append(1, 't'); - break; - default: - // escape and print as unicode codepoint - constexpr int printed_unicode_length = 6; // 4 hex + letter 'u' + \0 - std::array buf; - sprintf(buf.data(), "u%04x", ch); - out.append(buf.data(), buf.size() - 1); // add only five, discarding the null terminator. - break; - } - } - } - return out; -} - -AWS_LAMBDA_RUNTIME_API -invocation_response invocation_response::success(std::string const& payload, std::string const& content_type) -{ - invocation_response r; - r.m_success = true; - r.m_content_type = content_type; - r.m_payload = payload; - return r; -} - -AWS_LAMBDA_RUNTIME_API -invocation_response invocation_response::failure(std::string const& error_message, std::string const& error_type) -{ - invocation_response r; - 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":[]})"; - return r; -} - -} // namespace lambda_runtime -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/version.cpp.in b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/version.cpp.in deleted file mode 100644 index e13b1e189..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/src/version.cpp.in +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018-present Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#define AWS_LAMBDA_RUNTIME_API __attribute__((visibility("default"))) - -namespace aws { -namespace lambda_runtime { - -/* clang-format off */ -AWS_LAMBDA_RUNTIME_API -unsigned get_version_major() -{ - return @PROJECT_VERSION_MAJOR@; // NOLINT -} - -AWS_LAMBDA_RUNTIME_API -unsigned get_version_minor() -{ - return @PROJECT_VERSION_MINOR@; // NOLINT -} - -AWS_LAMBDA_RUNTIME_API -unsigned get_version_patch() -{ - return @PROJECT_VERSION_PATCH@; // NOLINT -} -/* clang-format on */ - -AWS_LAMBDA_RUNTIME_API -char const* get_version() -{ - return "@PROJECT_VERSION@"; -} - -} // namespace lambda_runtime -} // namespace aws diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/CMakeLists.txt deleted file mode 100644 index ddec1a809..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -project(aws-lambda-runtime-tests LANGUAGES CXX) -find_package(AWSSDK COMPONENTS lambda iam) - -add_executable(${PROJECT_NAME} - main.cpp - runtime_tests.cpp - 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}") # requires CMake 3.10 or later - -add_subdirectory(resources) - diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/.clang-tidy b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/.clang-tidy deleted file mode 100644 index 34fafc4c1..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/.clang-tidy +++ /dev/null @@ -1,3 +0,0 @@ ---- -Checks: '-*,llvm-twine-local' -... diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest-all.cc b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest-all.cc deleted file mode 100644 index 99d2fc62a..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest-all.cc +++ /dev/null @@ -1,11763 +0,0 @@ -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// Google C++ Testing and Mocking Framework (Google Test) -// -// Sometimes it's desirable to build Google Test by compiling a single file. -// This file serves this purpose. - -// This line ensures that gtest.h can be compiled on its own, even -// when it's fused. -#include "gtest.h" - -// The following lines pull in the real gtest *.cc files. -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// The Google C++ Testing and Mocking Framework (Google Test) - -// Copyright 2007, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// Utilities for testing Google Test itself and code that uses Google Test -// (e.g. frameworks built on top of Google Test). - -// GOOGLETEST_CM0004 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_ -#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_ - - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -namespace testing { - -// This helper class can be used to mock out Google Test failure reporting -// so that we can test Google Test or code that builds on Google Test. -// -// An object of this class appends a TestPartResult object to the -// TestPartResultArray object given in the constructor whenever a Google Test -// failure is reported. It can either intercept only failures that are -// generated in the same thread that created this object or it can intercept -// all generated failures. The scope of this mock object can be controlled with -// the second argument to the two arguments constructor. -class GTEST_API_ ScopedFakeTestPartResultReporter - : public TestPartResultReporterInterface { - public: - // The two possible mocking modes of this object. - enum InterceptMode { - INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. - INTERCEPT_ALL_THREADS // Intercepts all failures. - }; - - // The c'tor sets this object as the test part result reporter used - // by Google Test. The 'result' parameter specifies where to report the - // results. This reporter will only catch failures generated in the current - // thread. DEPRECATED - explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); - - // Same as above, but you can choose the interception scope of this object. - ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, - TestPartResultArray* result); - - // The d'tor restores the previous test part result reporter. - ~ScopedFakeTestPartResultReporter() override; - - // Appends the TestPartResult object to the TestPartResultArray - // received in the constructor. - // - // This method is from the TestPartResultReporterInterface - // interface. - void ReportTestPartResult(const TestPartResult& result) override; - - private: - void Init(); - - const InterceptMode intercept_mode_; - TestPartResultReporterInterface* old_reporter_; - TestPartResultArray* const result_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); -}; - -namespace internal { - -// A helper class for implementing EXPECT_FATAL_FAILURE() and -// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given -// TestPartResultArray contains exactly one failure that has the given -// type and contains the given substring. If that's not the case, a -// non-fatal failure will be generated. -class GTEST_API_ SingleFailureChecker { - public: - // The constructor remembers the arguments. - SingleFailureChecker(const TestPartResultArray* results, - TestPartResult::Type type, const std::string& substr); - ~SingleFailureChecker(); - private: - const TestPartResultArray* const results_; - const TestPartResult::Type type_; - const std::string substr_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); -}; - -} // namespace internal - -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -// A set of macros for testing Google Test assertions or code that's expected -// to generate Google Test fatal failures. It verifies that the given -// statement will cause exactly one fatal Google Test failure with 'substr' -// being part of the failure message. -// -// There are two different versions of this macro. EXPECT_FATAL_FAILURE only -// affects and considers failures generated in the current thread and -// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. -// -// The verification of the assertion is done correctly even when the statement -// throws an exception or aborts the current function. -// -// Known restrictions: -// - 'statement' cannot reference local non-static variables or -// non-static members of the current object. -// - 'statement' cannot return a value. -// - You cannot stream a failure message to this macro. -// -// Note that even though the implementations of the following two -// macros are much alike, we cannot refactor them to use a common -// helper macro, due to some peculiarity in how the preprocessor -// works. The AcceptsMacroThatExpandsToUnprotectedComma test in -// gtest_unittest.cc will fail to compile if we do that. -#define EXPECT_FATAL_FAILURE(statement, substr) \ - do { \ - class GTestExpectFatalFailureHelper {\ - public:\ - static void Execute() { statement; }\ - };\ - ::testing::TestPartResultArray gtest_failures;\ - ::testing::internal::SingleFailureChecker gtest_checker(\ - >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ - {\ - ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ - ::testing::ScopedFakeTestPartResultReporter:: \ - INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ - GTestExpectFatalFailureHelper::Execute();\ - }\ - } while (::testing::internal::AlwaysFalse()) - -#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ - do { \ - class GTestExpectFatalFailureHelper {\ - public:\ - static void Execute() { statement; }\ - };\ - ::testing::TestPartResultArray gtest_failures;\ - ::testing::internal::SingleFailureChecker gtest_checker(\ - >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ - {\ - ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ - ::testing::ScopedFakeTestPartResultReporter:: \ - INTERCEPT_ALL_THREADS, >est_failures);\ - GTestExpectFatalFailureHelper::Execute();\ - }\ - } while (::testing::internal::AlwaysFalse()) - -// A macro for testing Google Test assertions or code that's expected to -// generate Google Test non-fatal failures. It asserts that the given -// statement will cause exactly one non-fatal Google Test failure with 'substr' -// being part of the failure message. -// -// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only -// affects and considers failures generated in the current thread and -// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. -// -// 'statement' is allowed to reference local variables and members of -// the current object. -// -// The verification of the assertion is done correctly even when the statement -// throws an exception or aborts the current function. -// -// Known restrictions: -// - You cannot stream a failure message to this macro. -// -// Note that even though the implementations of the following two -// macros are much alike, we cannot refactor them to use a common -// helper macro, due to some peculiarity in how the preprocessor -// works. If we do that, the code won't compile when the user gives -// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that -// expands to code containing an unprotected comma. The -// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc -// catches that. -// -// For the same reason, we have to write -// if (::testing::internal::AlwaysTrue()) { statement; } -// instead of -// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) -// to avoid an MSVC warning on unreachable code. -#define EXPECT_NONFATAL_FAILURE(statement, substr) \ - do {\ - ::testing::TestPartResultArray gtest_failures;\ - ::testing::internal::SingleFailureChecker gtest_checker(\ - >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ - (substr));\ - {\ - ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ - ::testing::ScopedFakeTestPartResultReporter:: \ - INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ - if (::testing::internal::AlwaysTrue()) { statement; }\ - }\ - } while (::testing::internal::AlwaysFalse()) - -#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ - do {\ - ::testing::TestPartResultArray gtest_failures;\ - ::testing::internal::SingleFailureChecker gtest_checker(\ - >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ - (substr));\ - {\ - ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ - ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ - >est_failures);\ - if (::testing::internal::AlwaysTrue()) { statement; }\ - }\ - } while (::testing::internal::AlwaysFalse()) - -#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include // NOLINT -#include -#include - -#if GTEST_OS_LINUX - -# define GTEST_HAS_GETTIMEOFDAY_ 1 - -# include // NOLINT -# include // NOLINT -# include // NOLINT -// Declares vsnprintf(). This header is not available on Windows. -# include // NOLINT -# include // NOLINT -# include // NOLINT -# include // NOLINT -# include - -#elif GTEST_OS_ZOS -# define GTEST_HAS_GETTIMEOFDAY_ 1 -# include // NOLINT - -// On z/OS we additionally need strings.h for strcasecmp. -# include // NOLINT - -#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. - -# include // NOLINT -# undef min - -#elif GTEST_OS_WINDOWS // We are on Windows proper. - -# include // NOLINT -# include // NOLINT -# include // NOLINT -# include // NOLINT - -# if GTEST_OS_WINDOWS_MINGW -// MinGW has gettimeofday() but not _ftime64(). -# define GTEST_HAS_GETTIMEOFDAY_ 1 -# include // NOLINT -# endif // GTEST_OS_WINDOWS_MINGW - -// cpplint thinks that the header is already included, so we want to -// silence it. -# include // NOLINT -# undef min - -#else - -// Assume other platforms have gettimeofday(). -# define GTEST_HAS_GETTIMEOFDAY_ 1 - -// cpplint thinks that the header is already included, so we want to -// silence it. -# include // NOLINT -# include // NOLINT - -#endif // GTEST_OS_LINUX - -#if GTEST_HAS_EXCEPTIONS -# include -#endif - -#if GTEST_CAN_STREAM_RESULTS_ -# include // NOLINT -# include // NOLINT -# include // NOLINT -# include // NOLINT -#endif - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Utility functions and classes used by the Google C++ testing framework.// -// This file contains purely Google Test's internal implementation. Please -// DO NOT #INCLUDE IT IN A USER PROGRAM. - -#ifndef GTEST_SRC_GTEST_INTERNAL_INL_H_ -#define GTEST_SRC_GTEST_INTERNAL_INL_H_ - -#ifndef _WIN32_WCE -# include -#endif // !_WIN32_WCE -#include -#include // For strtoll/_strtoul64/malloc/free. -#include // For memmove. - -#include -#include -#include -#include - - -#if GTEST_CAN_STREAM_RESULTS_ -# include // NOLINT -# include // NOLINT -#endif - -#if GTEST_OS_WINDOWS -# include // NOLINT -#endif // GTEST_OS_WINDOWS - - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -namespace testing { - -// Declares the flags. -// -// We don't want the users to modify this flag in the code, but want -// Google Test's own unit tests to be able to access it. Therefore we -// declare it here as opposed to in gtest.h. -GTEST_DECLARE_bool_(death_test_use_fork); - -namespace internal { - -// The value of GetTestTypeId() as seen from within the Google Test -// library. This is solely for testing GetTestTypeId(). -GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; - -// Names of the flags (needed for parsing Google Test flags). -const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests"; -const char kBreakOnFailureFlag[] = "break_on_failure"; -const char kCatchExceptionsFlag[] = "catch_exceptions"; -const char kColorFlag[] = "color"; -const char kFilterFlag[] = "filter"; -const char kListTestsFlag[] = "list_tests"; -const char kOutputFlag[] = "output"; -const char kPrintTimeFlag[] = "print_time"; -const char kPrintUTF8Flag[] = "print_utf8"; -const char kRandomSeedFlag[] = "random_seed"; -const char kRepeatFlag[] = "repeat"; -const char kShuffleFlag[] = "shuffle"; -const char kStackTraceDepthFlag[] = "stack_trace_depth"; -const char kStreamResultToFlag[] = "stream_result_to"; -const char kThrowOnFailureFlag[] = "throw_on_failure"; -const char kFlagfileFlag[] = "flagfile"; - -// A valid random seed must be in [1, kMaxRandomSeed]. -const int kMaxRandomSeed = 99999; - -// g_help_flag is true iff the --help flag or an equivalent form is -// specified on the command line. -GTEST_API_ extern bool g_help_flag; - -// Returns the current time in milliseconds. -GTEST_API_ TimeInMillis GetTimeInMillis(); - -// Returns true iff Google Test should use colors in the output. -GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); - -// Formats the given time in milliseconds as seconds. -GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); - -// Converts the given time in milliseconds to a date string in the ISO 8601 -// format, without the timezone information. N.B.: due to the use the -// non-reentrant localtime() function, this function is not thread safe. Do -// not use it in any code that can be called from multiple threads. -GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms); - -// Parses a string for an Int32 flag, in the form of "--flag=value". -// -// On success, stores the value of the flag in *value, and returns -// true. On failure, returns false without changing *value. -GTEST_API_ bool ParseInt32Flag( - const char* str, const char* flag, Int32* value); - -// Returns a random seed in range [1, kMaxRandomSeed] based on the -// given --gtest_random_seed flag value. -inline int GetRandomSeedFromFlag(Int32 random_seed_flag) { - const unsigned int raw_seed = (random_seed_flag == 0) ? - static_cast(GetTimeInMillis()) : - static_cast(random_seed_flag); - - // Normalizes the actual seed to range [1, kMaxRandomSeed] such that - // it's easy to type. - const int normalized_seed = - static_cast((raw_seed - 1U) % - static_cast(kMaxRandomSeed)) + 1; - return normalized_seed; -} - -// Returns the first valid random seed after 'seed'. The behavior is -// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is -// considered to be 1. -inline int GetNextRandomSeed(int seed) { - GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) - << "Invalid random seed " << seed << " - must be in [1, " - << kMaxRandomSeed << "]."; - const int next_seed = seed + 1; - return (next_seed > kMaxRandomSeed) ? 1 : next_seed; -} - -// This class saves the values of all Google Test flags in its c'tor, and -// restores them in its d'tor. -class GTestFlagSaver { - public: - // The c'tor. - GTestFlagSaver() { - also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests); - break_on_failure_ = GTEST_FLAG(break_on_failure); - catch_exceptions_ = GTEST_FLAG(catch_exceptions); - color_ = GTEST_FLAG(color); - death_test_style_ = GTEST_FLAG(death_test_style); - death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); - filter_ = GTEST_FLAG(filter); - internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); - list_tests_ = GTEST_FLAG(list_tests); - output_ = GTEST_FLAG(output); - print_time_ = GTEST_FLAG(print_time); - print_utf8_ = GTEST_FLAG(print_utf8); - random_seed_ = GTEST_FLAG(random_seed); - repeat_ = GTEST_FLAG(repeat); - shuffle_ = GTEST_FLAG(shuffle); - stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); - stream_result_to_ = GTEST_FLAG(stream_result_to); - throw_on_failure_ = GTEST_FLAG(throw_on_failure); - } - - // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. - ~GTestFlagSaver() { - GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_; - GTEST_FLAG(break_on_failure) = break_on_failure_; - GTEST_FLAG(catch_exceptions) = catch_exceptions_; - GTEST_FLAG(color) = color_; - GTEST_FLAG(death_test_style) = death_test_style_; - GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; - GTEST_FLAG(filter) = filter_; - GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; - GTEST_FLAG(list_tests) = list_tests_; - GTEST_FLAG(output) = output_; - GTEST_FLAG(print_time) = print_time_; - GTEST_FLAG(print_utf8) = print_utf8_; - GTEST_FLAG(random_seed) = random_seed_; - GTEST_FLAG(repeat) = repeat_; - GTEST_FLAG(shuffle) = shuffle_; - GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; - GTEST_FLAG(stream_result_to) = stream_result_to_; - GTEST_FLAG(throw_on_failure) = throw_on_failure_; - } - - private: - // Fields for saving the original values of flags. - bool also_run_disabled_tests_; - bool break_on_failure_; - bool catch_exceptions_; - std::string color_; - std::string death_test_style_; - bool death_test_use_fork_; - std::string filter_; - std::string internal_run_death_test_; - bool list_tests_; - std::string output_; - bool print_time_; - bool print_utf8_; - internal::Int32 random_seed_; - internal::Int32 repeat_; - bool shuffle_; - internal::Int32 stack_trace_depth_; - std::string stream_result_to_; - bool throw_on_failure_; -} GTEST_ATTRIBUTE_UNUSED_; - -// Converts a Unicode code point to a narrow string in UTF-8 encoding. -// code_point parameter is of type UInt32 because wchar_t may not be -// wide enough to contain a code point. -// If the code_point is not a valid Unicode code point -// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted -// to "(Invalid Unicode 0xXXXXXXXX)". -GTEST_API_ std::string CodePointToUtf8(UInt32 code_point); - -// Converts a wide string to a narrow string in UTF-8 encoding. -// The wide string is assumed to have the following encoding: -// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) -// UTF-32 if sizeof(wchar_t) == 4 (on Linux) -// Parameter str points to a null-terminated wide string. -// Parameter num_chars may additionally limit the number -// of wchar_t characters processed. -1 is used when the entire string -// should be processed. -// If the string contains code points that are not valid Unicode code points -// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output -// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding -// and contains invalid UTF-16 surrogate pairs, values in those pairs -// will be encoded as individual Unicode characters from Basic Normal Plane. -GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars); - -// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file -// if the variable is present. If a file already exists at this location, this -// function will write over it. If the variable is present, but the file cannot -// be created, prints an error and exits. -void WriteToShardStatusFileIfNeeded(); - -// Checks whether sharding is enabled by examining the relevant -// environment variable values. If the variables are present, -// but inconsistent (e.g., shard_index >= total_shards), prints -// an error and exits. If in_subprocess_for_death_test, sharding is -// disabled because it must only be applied to the original test -// process. Otherwise, we could filter out death tests we intended to execute. -GTEST_API_ bool ShouldShard(const char* total_shards_str, - const char* shard_index_str, - bool in_subprocess_for_death_test); - -// Parses the environment variable var as an Int32. If it is unset, -// returns default_val. If it is not an Int32, prints an error and -// and aborts. -GTEST_API_ Int32 Int32FromEnvOrDie(const char* env_var, Int32 default_val); - -// Given the total number of shards, the shard index, and the test id, -// returns true iff the test should be run on this shard. The test id is -// some arbitrary but unique non-negative integer assigned to each test -// method. Assumes that 0 <= shard_index < total_shards. -GTEST_API_ bool ShouldRunTestOnShard( - int total_shards, int shard_index, int test_id); - -// STL container utilities. - -// Returns the number of elements in the given container that satisfy -// the given predicate. -template -inline int CountIf(const Container& c, Predicate predicate) { - // Implemented as an explicit loop since std::count_if() in libCstd on - // Solaris has a non-standard signature. - int count = 0; - for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) { - if (predicate(*it)) - ++count; - } - return count; -} - -// Applies a function/functor to each element in the container. -template -void ForEach(const Container& c, Functor functor) { - std::for_each(c.begin(), c.end(), functor); -} - -// Returns the i-th element of the vector, or default_value if i is not -// in range [0, v.size()). -template -inline E GetElementOr(const std::vector& v, int i, E default_value) { - return (i < 0 || i >= static_cast(v.size())) ? default_value - : v[static_cast(i)]; -} - -// Performs an in-place shuffle of a range of the vector's elements. -// 'begin' and 'end' are element indices as an STL-style range; -// i.e. [begin, end) are shuffled, where 'end' == size() means to -// shuffle to the end of the vector. -template -void ShuffleRange(internal::Random* random, int begin, int end, - std::vector* v) { - const int size = static_cast(v->size()); - GTEST_CHECK_(0 <= begin && begin <= size) - << "Invalid shuffle range start " << begin << ": must be in range [0, " - << size << "]."; - GTEST_CHECK_(begin <= end && end <= size) - << "Invalid shuffle range finish " << end << ": must be in range [" - << begin << ", " << size << "]."; - - // Fisher-Yates shuffle, from - // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle - for (int range_width = end - begin; range_width >= 2; range_width--) { - const int last_in_range = begin + range_width - 1; - const int selected = - begin + - static_cast(random->Generate(static_cast(range_width))); - std::swap((*v)[static_cast(selected)], - (*v)[static_cast(last_in_range)]); - } -} - -// Performs an in-place shuffle of the vector's elements. -template -inline void Shuffle(internal::Random* random, std::vector* v) { - ShuffleRange(random, 0, static_cast(v->size()), v); -} - -// A function for deleting an object. Handy for being used as a -// functor. -template -static void Delete(T* x) { - delete x; -} - -// A predicate that checks the key of a TestProperty against a known key. -// -// TestPropertyKeyIs is copyable. -class TestPropertyKeyIs { - public: - // Constructor. - // - // TestPropertyKeyIs has NO default constructor. - explicit TestPropertyKeyIs(const std::string& key) : key_(key) {} - - // Returns true iff the test name of test property matches on key_. - bool operator()(const TestProperty& test_property) const { - return test_property.key() == key_; - } - - private: - std::string key_; -}; - -// Class UnitTestOptions. -// -// This class contains functions for processing options the user -// specifies when running the tests. It has only static members. -// -// In most cases, the user can specify an option using either an -// environment variable or a command line flag. E.g. you can set the -// test filter using either GTEST_FILTER or --gtest_filter. If both -// the variable and the flag are present, the latter overrides the -// former. -class GTEST_API_ UnitTestOptions { - public: - // Functions for processing the gtest_output flag. - - // Returns the output format, or "" for normal printed output. - static std::string GetOutputFormat(); - - // Returns the absolute path of the requested output file, or the - // default (test_detail.xml in the original working directory) if - // none was explicitly specified. - static std::string GetAbsolutePathToOutputFile(); - - // Functions for processing the gtest_filter flag. - - // Returns true iff the wildcard pattern matches the string. The - // first ':' or '\0' character in pattern marks the end of it. - // - // This recursive algorithm isn't very efficient, but is clear and - // works well enough for matching test names, which are short. - static bool PatternMatchesString(const char *pattern, const char *str); - - // Returns true iff the user-specified filter matches the test suite - // name and the test name. - static bool FilterMatchesTest(const std::string& test_suite_name, - const std::string& test_name); - -#if GTEST_OS_WINDOWS - // Function for supporting the gtest_catch_exception flag. - - // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the - // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. - // This function is useful as an __except condition. - static int GTestShouldProcessSEH(DWORD exception_code); -#endif // GTEST_OS_WINDOWS - - // Returns true if "name" matches the ':' separated list of glob-style - // filters in "filter". - static bool MatchesFilter(const std::string& name, const char* filter); -}; - -// Returns the current application's name, removing directory path if that -// is present. Used by UnitTestOptions::GetOutputFile. -GTEST_API_ FilePath GetCurrentExecutableName(); - -// The role interface for getting the OS stack trace as a string. -class OsStackTraceGetterInterface { - public: - OsStackTraceGetterInterface() {} - virtual ~OsStackTraceGetterInterface() {} - - // Returns the current OS stack trace as an std::string. Parameters: - // - // max_depth - the maximum number of stack frames to be included - // in the trace. - // skip_count - the number of top frames to be skipped; doesn't count - // against max_depth. - virtual std::string CurrentStackTrace(int max_depth, int skip_count) = 0; - - // UponLeavingGTest() should be called immediately before Google Test calls - // user code. It saves some information about the current stack that - // CurrentStackTrace() will use to find and hide Google Test stack frames. - virtual void UponLeavingGTest() = 0; - - // This string is inserted in place of stack frames that are part of - // Google Test's implementation. - static const char* const kElidedFramesMarker; - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); -}; - -// A working implementation of the OsStackTraceGetterInterface interface. -class OsStackTraceGetter : public OsStackTraceGetterInterface { - public: - OsStackTraceGetter() {} - - std::string CurrentStackTrace(int max_depth, int skip_count) override; - void UponLeavingGTest() override; - - private: -#if GTEST_HAS_ABSL - Mutex mutex_; // Protects all internal state. - - // We save the stack frame below the frame that calls user code. - // We do this because the address of the frame immediately below - // the user code changes between the call to UponLeavingGTest() - // and any calls to the stack trace code from within the user code. - void* caller_frame_ = nullptr; -#endif // GTEST_HAS_ABSL - - GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); -}; - -// Information about a Google Test trace point. -struct TraceInfo { - const char* file; - int line; - std::string message; -}; - -// This is the default global test part result reporter used in UnitTestImpl. -// This class should only be used by UnitTestImpl. -class DefaultGlobalTestPartResultReporter - : public TestPartResultReporterInterface { - public: - explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); - // Implements the TestPartResultReporterInterface. Reports the test part - // result in the current test. - void ReportTestPartResult(const TestPartResult& result) override; - - private: - UnitTestImpl* const unit_test_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter); -}; - -// This is the default per thread test part result reporter used in -// UnitTestImpl. This class should only be used by UnitTestImpl. -class DefaultPerThreadTestPartResultReporter - : public TestPartResultReporterInterface { - public: - explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); - // Implements the TestPartResultReporterInterface. The implementation just - // delegates to the current global test part result reporter of *unit_test_. - void ReportTestPartResult(const TestPartResult& result) override; - - private: - UnitTestImpl* const unit_test_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter); -}; - -// The private implementation of the UnitTest class. We don't protect -// the methods under a mutex, as this class is not accessible by a -// user and the UnitTest class that delegates work to this class does -// proper locking. -class GTEST_API_ UnitTestImpl { - public: - explicit UnitTestImpl(UnitTest* parent); - virtual ~UnitTestImpl(); - - // There are two different ways to register your own TestPartResultReporter. - // You can register your own repoter to listen either only for test results - // from the current thread or for results from all threads. - // By default, each per-thread test result repoter just passes a new - // TestPartResult to the global test result reporter, which registers the - // test part result for the currently running test. - - // Returns the global test part result reporter. - TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); - - // Sets the global test part result reporter. - void SetGlobalTestPartResultReporter( - TestPartResultReporterInterface* reporter); - - // Returns the test part result reporter for the current thread. - TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); - - // Sets the test part result reporter for the current thread. - void SetTestPartResultReporterForCurrentThread( - TestPartResultReporterInterface* reporter); - - // Gets the number of successful test suites. - int successful_test_suite_count() const; - - // Gets the number of failed test suites. - int failed_test_suite_count() const; - - // Gets the number of all test suites. - int total_test_suite_count() const; - - // Gets the number of all test suites that contain at least one test - // that should run. - int test_suite_to_run_count() const; - - // Gets the number of successful tests. - int successful_test_count() const; - - // Gets the number of skipped tests. - int skipped_test_count() const; - - // Gets the number of failed tests. - int failed_test_count() const; - - // Gets the number of disabled tests that will be reported in the XML report. - int reportable_disabled_test_count() const; - - // Gets the number of disabled tests. - int disabled_test_count() const; - - // Gets the number of tests to be printed in the XML report. - int reportable_test_count() const; - - // Gets the number of all tests. - int total_test_count() const; - - // Gets the number of tests that should run. - int test_to_run_count() const; - - // Gets the time of the test program start, in ms from the start of the - // UNIX epoch. - TimeInMillis start_timestamp() const { return start_timestamp_; } - - // Gets the elapsed time, in milliseconds. - TimeInMillis elapsed_time() const { return elapsed_time_; } - - // Returns true iff the unit test passed (i.e. all test suites passed). - bool Passed() const { return !Failed(); } - - // Returns true iff the unit test failed (i.e. some test suite failed - // or something outside of all tests failed). - bool Failed() const { - return failed_test_suite_count() > 0 || ad_hoc_test_result()->Failed(); - } - - // Gets the i-th test suite among all the test suites. i can range from 0 to - // total_test_suite_count() - 1. If i is not in that range, returns NULL. - const TestSuite* GetTestSuite(int i) const { - const int index = GetElementOr(test_suite_indices_, i, -1); - return index < 0 ? nullptr : test_suites_[static_cast(i)]; - } - - // Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - const TestCase* GetTestCase(int i) const { return GetTestSuite(i); } -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Gets the i-th test suite among all the test suites. i can range from 0 to - // total_test_suite_count() - 1. If i is not in that range, returns NULL. - TestSuite* GetMutableSuiteCase(int i) { - const int index = GetElementOr(test_suite_indices_, i, -1); - return index < 0 ? nullptr : test_suites_[static_cast(index)]; - } - - // Provides access to the event listener list. - TestEventListeners* listeners() { return &listeners_; } - - // Returns the TestResult for the test that's currently running, or - // the TestResult for the ad hoc test if no test is running. - TestResult* current_test_result(); - - // Returns the TestResult for the ad hoc test. - const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } - - // Sets the OS stack trace getter. - // - // Does nothing if the input and the current OS stack trace getter - // are the same; otherwise, deletes the old getter and makes the - // input the current getter. - void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); - - // Returns the current OS stack trace getter if it is not NULL; - // otherwise, creates an OsStackTraceGetter, makes it the current - // getter, and returns it. - OsStackTraceGetterInterface* os_stack_trace_getter(); - - // Returns the current OS stack trace as an std::string. - // - // The maximum number of stack frames to be included is specified by - // the gtest_stack_trace_depth flag. The skip_count parameter - // specifies the number of top frames to be skipped, which doesn't - // count against the number of frames to be included. - // - // For example, if Foo() calls Bar(), which in turn calls - // CurrentOsStackTraceExceptTop(1), Foo() will be included in the - // trace but Bar() and CurrentOsStackTraceExceptTop() won't. - std::string CurrentOsStackTraceExceptTop(int skip_count) GTEST_NO_INLINE_; - - // Finds and returns a TestSuite with the given name. If one doesn't - // exist, creates one and returns it. - // - // Arguments: - // - // test_suite_name: name of the test suite - // type_param: the name of the test's type parameter, or NULL if - // this is not a typed or a type-parameterized test. - // set_up_tc: pointer to the function that sets up the test suite - // tear_down_tc: pointer to the function that tears down the test suite - TestSuite* GetTestSuite(const char* test_suite_name, const char* type_param, - internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc); - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - TestCase* GetTestCase(const char* test_case_name, const char* type_param, - internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc) { - return GetTestSuite(test_case_name, type_param, set_up_tc, tear_down_tc); - } -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Adds a TestInfo to the unit test. - // - // Arguments: - // - // set_up_tc: pointer to the function that sets up the test suite - // tear_down_tc: pointer to the function that tears down the test suite - // test_info: the TestInfo object - void AddTestInfo(internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc, - TestInfo* test_info) { - // In order to support thread-safe death tests, we need to - // remember the original working directory when the test program - // was first invoked. We cannot do this in RUN_ALL_TESTS(), as - // the user may have changed the current directory before calling - // RUN_ALL_TESTS(). Therefore we capture the current directory in - // AddTestInfo(), which is called to register a TEST or TEST_F - // before main() is reached. - if (original_working_dir_.IsEmpty()) { - original_working_dir_.Set(FilePath::GetCurrentDir()); - GTEST_CHECK_(!original_working_dir_.IsEmpty()) - << "Failed to get the current working directory."; - } - - GetTestSuite(test_info->test_suite_name(), test_info->type_param(), - set_up_tc, tear_down_tc) - ->AddTestInfo(test_info); - } - - // Returns ParameterizedTestSuiteRegistry object used to keep track of - // value-parameterized tests and instantiate and register them. - internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() { - return parameterized_test_registry_; - } - - // Sets the TestSuite object for the test that's currently running. - void set_current_test_suite(TestSuite* a_current_test_suite) { - current_test_suite_ = a_current_test_suite; - } - - // Sets the TestInfo object for the test that's currently running. If - // current_test_info is NULL, the assertion results will be stored in - // ad_hoc_test_result_. - void set_current_test_info(TestInfo* a_current_test_info) { - current_test_info_ = a_current_test_info; - } - - // Registers all parameterized tests defined using TEST_P and - // INSTANTIATE_TEST_SUITE_P, creating regular tests for each test/parameter - // combination. This method can be called more then once; it has guards - // protecting from registering the tests more then once. If - // value-parameterized tests are disabled, RegisterParameterizedTests is - // present but does nothing. - void RegisterParameterizedTests(); - - // Runs all tests in this UnitTest object, prints the result, and - // returns true if all tests are successful. If any exception is - // thrown during a test, this test is considered to be failed, but - // the rest of the tests will still be run. - bool RunAllTests(); - - // Clears the results of all tests, except the ad hoc tests. - void ClearNonAdHocTestResult() { - ForEach(test_suites_, TestSuite::ClearTestSuiteResult); - } - - // Clears the results of ad-hoc test assertions. - void ClearAdHocTestResult() { - ad_hoc_test_result_.Clear(); - } - - // Adds a TestProperty to the current TestResult object when invoked in a - // context of a test or a test suite, or to the global property set. If the - // result already contains a property with the same key, the value will be - // updated. - void RecordProperty(const TestProperty& test_property); - - enum ReactionToSharding { - HONOR_SHARDING_PROTOCOL, - IGNORE_SHARDING_PROTOCOL - }; - - // Matches the full name of each test against the user-specified - // filter to decide whether the test should run, then records the - // result in each TestSuite and TestInfo object. - // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests - // based on sharding variables in the environment. - // Returns the number of tests that should run. - int FilterTests(ReactionToSharding shard_tests); - - // Prints the names of the tests matching the user-specified filter flag. - void ListTestsMatchingFilter(); - - const TestSuite* current_test_suite() const { return current_test_suite_; } - TestInfo* current_test_info() { return current_test_info_; } - const TestInfo* current_test_info() const { return current_test_info_; } - - // Returns the vector of environments that need to be set-up/torn-down - // before/after the tests are run. - std::vector& environments() { return environments_; } - - // Getters for the per-thread Google Test trace stack. - std::vector& gtest_trace_stack() { - return *(gtest_trace_stack_.pointer()); - } - const std::vector& gtest_trace_stack() const { - return gtest_trace_stack_.get(); - } - -#if GTEST_HAS_DEATH_TEST - void InitDeathTestSubprocessControlInfo() { - internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); - } - // Returns a pointer to the parsed --gtest_internal_run_death_test - // flag, or NULL if that flag was not specified. - // This information is useful only in a death test child process. - // Must not be called before a call to InitGoogleTest. - const InternalRunDeathTestFlag* internal_run_death_test_flag() const { - return internal_run_death_test_flag_.get(); - } - - // Returns a pointer to the current death test factory. - internal::DeathTestFactory* death_test_factory() { - return death_test_factory_.get(); - } - - void SuppressTestEventsIfInSubprocess(); - - friend class ReplaceDeathTestFactory; -#endif // GTEST_HAS_DEATH_TEST - - // Initializes the event listener performing XML output as specified by - // UnitTestOptions. Must not be called before InitGoogleTest. - void ConfigureXmlOutput(); - -#if GTEST_CAN_STREAM_RESULTS_ - // Initializes the event listener for streaming test results to a socket. - // Must not be called before InitGoogleTest. - void ConfigureStreamingOutput(); -#endif - - // Performs initialization dependent upon flag values obtained in - // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to - // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest - // this function is also called from RunAllTests. Since this function can be - // called more than once, it has to be idempotent. - void PostFlagParsingInit(); - - // Gets the random seed used at the start of the current test iteration. - int random_seed() const { return random_seed_; } - - // Gets the random number generator. - internal::Random* random() { return &random_; } - - // Shuffles all test suites, and the tests within each test suite, - // making sure that death tests are still run first. - void ShuffleTests(); - - // Restores the test suites and tests to their order before the first shuffle. - void UnshuffleTests(); - - // Returns the value of GTEST_FLAG(catch_exceptions) at the moment - // UnitTest::Run() starts. - bool catch_exceptions() const { return catch_exceptions_; } - - private: - friend class ::testing::UnitTest; - - // Used by UnitTest::Run() to capture the state of - // GTEST_FLAG(catch_exceptions) at the moment it starts. - void set_catch_exceptions(bool value) { catch_exceptions_ = value; } - - // The UnitTest object that owns this implementation object. - UnitTest* const parent_; - - // The working directory when the first TEST() or TEST_F() was - // executed. - internal::FilePath original_working_dir_; - - // The default test part result reporters. - DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; - DefaultPerThreadTestPartResultReporter - default_per_thread_test_part_result_reporter_; - - // Points to (but doesn't own) the global test part result reporter. - TestPartResultReporterInterface* global_test_part_result_repoter_; - - // Protects read and write access to global_test_part_result_reporter_. - internal::Mutex global_test_part_result_reporter_mutex_; - - // Points to (but doesn't own) the per-thread test part result reporter. - internal::ThreadLocal - per_thread_test_part_result_reporter_; - - // The vector of environments that need to be set-up/torn-down - // before/after the tests are run. - std::vector environments_; - - // The vector of TestSuites in their original order. It owns the - // elements in the vector. - std::vector test_suites_; - - // Provides a level of indirection for the test suite list to allow - // easy shuffling and restoring the test suite order. The i-th - // element of this vector is the index of the i-th test suite in the - // shuffled order. - std::vector test_suite_indices_; - - // ParameterizedTestRegistry object used to register value-parameterized - // tests. - internal::ParameterizedTestSuiteRegistry parameterized_test_registry_; - - // Indicates whether RegisterParameterizedTests() has been called already. - bool parameterized_tests_registered_; - - // Index of the last death test suite registered. Initially -1. - int last_death_test_suite_; - - // This points to the TestSuite for the currently running test. It - // changes as Google Test goes through one test suite after another. - // When no test is running, this is set to NULL and Google Test - // stores assertion results in ad_hoc_test_result_. Initially NULL. - TestSuite* current_test_suite_; - - // This points to the TestInfo for the currently running test. It - // changes as Google Test goes through one test after another. When - // no test is running, this is set to NULL and Google Test stores - // assertion results in ad_hoc_test_result_. Initially NULL. - TestInfo* current_test_info_; - - // Normally, a user only writes assertions inside a TEST or TEST_F, - // or inside a function called by a TEST or TEST_F. Since Google - // Test keeps track of which test is current running, it can - // associate such an assertion with the test it belongs to. - // - // If an assertion is encountered when no TEST or TEST_F is running, - // Google Test attributes the assertion result to an imaginary "ad hoc" - // test, and records the result in ad_hoc_test_result_. - TestResult ad_hoc_test_result_; - - // The list of event listeners that can be used to track events inside - // Google Test. - TestEventListeners listeners_; - - // The OS stack trace getter. Will be deleted when the UnitTest - // object is destructed. By default, an OsStackTraceGetter is used, - // but the user can set this field to use a custom getter if that is - // desired. - OsStackTraceGetterInterface* os_stack_trace_getter_; - - // True iff PostFlagParsingInit() has been called. - bool post_flag_parse_init_performed_; - - // The random number seed used at the beginning of the test run. - int random_seed_; - - // Our random number generator. - internal::Random random_; - - // The time of the test program start, in ms from the start of the - // UNIX epoch. - TimeInMillis start_timestamp_; - - // How long the test took to run, in milliseconds. - TimeInMillis elapsed_time_; - -#if GTEST_HAS_DEATH_TEST - // The decomposed components of the gtest_internal_run_death_test flag, - // parsed when RUN_ALL_TESTS is called. - std::unique_ptr internal_run_death_test_flag_; - std::unique_ptr death_test_factory_; -#endif // GTEST_HAS_DEATH_TEST - - // A per-thread stack of traces created by the SCOPED_TRACE() macro. - internal::ThreadLocal > gtest_trace_stack_; - - // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() - // starts. - bool catch_exceptions_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); -}; // class UnitTestImpl - -// Convenience function for accessing the global UnitTest -// implementation object. -inline UnitTestImpl* GetUnitTestImpl() { - return UnitTest::GetInstance()->impl(); -} - -#if GTEST_USES_SIMPLE_RE - -// Internal helper functions for implementing the simple regular -// expression matcher. -GTEST_API_ bool IsInSet(char ch, const char* str); -GTEST_API_ bool IsAsciiDigit(char ch); -GTEST_API_ bool IsAsciiPunct(char ch); -GTEST_API_ bool IsRepeat(char ch); -GTEST_API_ bool IsAsciiWhiteSpace(char ch); -GTEST_API_ bool IsAsciiWordChar(char ch); -GTEST_API_ bool IsValidEscape(char ch); -GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); -GTEST_API_ bool ValidateRegex(const char* regex); -GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); -GTEST_API_ bool MatchRepetitionAndRegexAtHead( - bool escaped, char ch, char repeat, const char* regex, const char* str); -GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); - -#endif // GTEST_USES_SIMPLE_RE - -// Parses the command line for Google Test flags, without initializing -// other parts of Google Test. -GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); -GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); - -#if GTEST_HAS_DEATH_TEST - -// Returns the message describing the last system error, regardless of the -// platform. -GTEST_API_ std::string GetLastErrnoDescription(); - -// Attempts to parse a string into a positive integer pointed to by the -// number parameter. Returns true if that is possible. -// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use -// it here. -template -bool ParseNaturalNumber(const ::std::string& str, Integer* number) { - // Fail fast if the given string does not begin with a digit; - // this bypasses strtoXXX's "optional leading whitespace and plus - // or minus sign" semantics, which are undesirable here. - if (str.empty() || !IsDigit(str[0])) { - return false; - } - errno = 0; - - char* end; - // BiggestConvertible is the largest integer type that system-provided - // string-to-number conversion routines can return. - -# if GTEST_OS_WINDOWS && !defined(__GNUC__) - - // MSVC and C++ Builder define __int64 instead of the standard long long. - typedef unsigned __int64 BiggestConvertible; - const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10); - -# else - - typedef unsigned long long BiggestConvertible; // NOLINT - const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); - -# endif // GTEST_OS_WINDOWS && !defined(__GNUC__) - - const bool parse_success = *end == '\0' && errno == 0; - - GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); - - const Integer result = static_cast(parsed); - if (parse_success && static_cast(result) == parsed) { - *number = result; - return true; - } - return false; -} -#endif // GTEST_HAS_DEATH_TEST - -// TestResult contains some private methods that should be hidden from -// Google Test user but are required for testing. This class allow our tests -// to access them. -// -// This class is supplied only for the purpose of testing Google Test's own -// constructs. Do not use it in user tests, either directly or indirectly. -class TestResultAccessor { - public: - static void RecordProperty(TestResult* test_result, - const std::string& xml_element, - const TestProperty& property) { - test_result->RecordProperty(xml_element, property); - } - - static void ClearTestPartResults(TestResult* test_result) { - test_result->ClearTestPartResults(); - } - - static const std::vector& test_part_results( - const TestResult& test_result) { - return test_result.test_part_results(); - } -}; - -#if GTEST_CAN_STREAM_RESULTS_ - -// Streams test results to the given port on the given host machine. -class StreamingListener : public EmptyTestEventListener { - public: - // Abstract base class for writing strings to a socket. - class AbstractSocketWriter { - public: - virtual ~AbstractSocketWriter() {} - - // Sends a string to the socket. - virtual void Send(const std::string& message) = 0; - - // Closes the socket. - virtual void CloseConnection() {} - - // Sends a string and a newline to the socket. - void SendLn(const std::string& message) { Send(message + "\n"); } - }; - - // Concrete class for actually writing strings to a socket. - class SocketWriter : public AbstractSocketWriter { - public: - SocketWriter(const std::string& host, const std::string& port) - : sockfd_(-1), host_name_(host), port_num_(port) { - MakeConnection(); - } - - ~SocketWriter() override { - if (sockfd_ != -1) - CloseConnection(); - } - - // Sends a string to the socket. - void Send(const std::string& message) override { - GTEST_CHECK_(sockfd_ != -1) - << "Send() can be called only when there is a connection."; - - const auto len = static_cast(message.length()); - if (write(sockfd_, message.c_str(), len) != static_cast(len)) { - GTEST_LOG_(WARNING) - << "stream_result_to: failed to stream to " - << host_name_ << ":" << port_num_; - } - } - - private: - // Creates a client socket and connects to the server. - void MakeConnection(); - - // Closes the socket. - void CloseConnection() override { - GTEST_CHECK_(sockfd_ != -1) - << "CloseConnection() can be called only when there is a connection."; - - close(sockfd_); - sockfd_ = -1; - } - - int sockfd_; // socket file descriptor - const std::string host_name_; - const std::string port_num_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(SocketWriter); - }; // class SocketWriter - - // Escapes '=', '&', '%', and '\n' characters in str as "%xx". - static std::string UrlEncode(const char* str); - - StreamingListener(const std::string& host, const std::string& port) - : socket_writer_(new SocketWriter(host, port)) { - Start(); - } - - explicit StreamingListener(AbstractSocketWriter* socket_writer) - : socket_writer_(socket_writer) { Start(); } - - void OnTestProgramStart(const UnitTest& /* unit_test */) override { - SendLn("event=TestProgramStart"); - } - - void OnTestProgramEnd(const UnitTest& unit_test) override { - // Note that Google Test current only report elapsed time for each - // test iteration, not for the entire test program. - SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed())); - - // Notify the streaming server to stop. - socket_writer_->CloseConnection(); - } - - void OnTestIterationStart(const UnitTest& /* unit_test */, - int iteration) override { - SendLn("event=TestIterationStart&iteration=" + - StreamableToString(iteration)); - } - - void OnTestIterationEnd(const UnitTest& unit_test, - int /* iteration */) override { - SendLn("event=TestIterationEnd&passed=" + - FormatBool(unit_test.Passed()) + "&elapsed_time=" + - StreamableToString(unit_test.elapsed_time()) + "ms"); - } - - // Note that "event=TestCaseStart" is a wire format and has to remain - // "case" for compatibilty - void OnTestCaseStart(const TestCase& test_case) override { - SendLn(std::string("event=TestCaseStart&name=") + test_case.name()); - } - - // Note that "event=TestCaseEnd" is a wire format and has to remain - // "case" for compatibilty - void OnTestCaseEnd(const TestCase& test_case) override { - SendLn("event=TestCaseEnd&passed=" + FormatBool(test_case.Passed()) + - "&elapsed_time=" + StreamableToString(test_case.elapsed_time()) + - "ms"); - } - - void OnTestStart(const TestInfo& test_info) override { - SendLn(std::string("event=TestStart&name=") + test_info.name()); - } - - void OnTestEnd(const TestInfo& test_info) override { - SendLn("event=TestEnd&passed=" + - FormatBool((test_info.result())->Passed()) + - "&elapsed_time=" + - StreamableToString((test_info.result())->elapsed_time()) + "ms"); - } - - void OnTestPartResult(const TestPartResult& test_part_result) override { - const char* file_name = test_part_result.file_name(); - if (file_name == nullptr) file_name = ""; - SendLn("event=TestPartResult&file=" + UrlEncode(file_name) + - "&line=" + StreamableToString(test_part_result.line_number()) + - "&message=" + UrlEncode(test_part_result.message())); - } - - private: - // Sends the given message and a newline to the socket. - void SendLn(const std::string& message) { socket_writer_->SendLn(message); } - - // Called at the start of streaming to notify the receiver what - // protocol we are using. - void Start() { SendLn("gtest_streaming_protocol_version=1.0"); } - - std::string FormatBool(bool value) { return value ? "1" : "0"; } - - const std::unique_ptr socket_writer_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener); -}; // class StreamingListener - -#endif // GTEST_CAN_STREAM_RESULTS_ - -} // namespace internal -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -#endif // GTEST_SRC_GTEST_INTERNAL_INL_H_ - -#if GTEST_OS_WINDOWS -# define vsnprintf _vsnprintf -#endif // GTEST_OS_WINDOWS - -#if GTEST_OS_MAC -#ifndef GTEST_OS_IOS -#include -#endif -#endif - -#if GTEST_HAS_ABSL -#include "absl/debugging/failure_signal_handler.h" -#include "absl/debugging/stacktrace.h" -#include "absl/debugging/symbolize.h" -#include "absl/strings/str_cat.h" -#endif // GTEST_HAS_ABSL - -namespace testing { - -using internal::CountIf; -using internal::ForEach; -using internal::GetElementOr; -using internal::Shuffle; - -// Constants. - -// A test whose test suite name or test name matches this filter is -// disabled and not run. -static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; - -// A test suite whose name matches this filter is considered a death -// test suite and will be run before test suites whose name doesn't -// match this filter. -static const char kDeathTestSuiteFilter[] = "*DeathTest:*DeathTest/*"; - -// A test filter that matches everything. -static const char kUniversalFilter[] = "*"; - -// The default output format. -static const char kDefaultOutputFormat[] = "xml"; -// The default output file. -static const char kDefaultOutputFile[] = "test_detail"; - -// The environment variable name for the test shard index. -static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; -// The environment variable name for the total number of test shards. -static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; -// The environment variable name for the test shard status file. -static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; - -namespace internal { - -// The text used in failure messages to indicate the start of the -// stack trace. -const char kStackTraceMarker[] = "\nStack trace:\n"; - -// g_help_flag is true iff the --help flag or an equivalent form is -// specified on the command line. -bool g_help_flag = false; - -// Utilty function to Open File for Writing -static FILE* OpenFileForWriting(const std::string& output_file) { - FILE* fileout = nullptr; - FilePath output_file_path(output_file); - FilePath output_dir(output_file_path.RemoveFileName()); - - if (output_dir.CreateDirectoriesRecursively()) { - fileout = posix::FOpen(output_file.c_str(), "w"); - } - if (fileout == nullptr) { - GTEST_LOG_(FATAL) << "Unable to open file \"" << output_file << "\""; - } - return fileout; -} - -} // namespace internal - -// Bazel passes in the argument to '--test_filter' via the TESTBRIDGE_TEST_ONLY -// environment variable. -static const char* GetDefaultFilter() { - const char* const testbridge_test_only = - internal::posix::GetEnv("TESTBRIDGE_TEST_ONLY"); - if (testbridge_test_only != nullptr) { - return testbridge_test_only; - } - return kUniversalFilter; -} - -GTEST_DEFINE_bool_( - also_run_disabled_tests, - internal::BoolFromGTestEnv("also_run_disabled_tests", false), - "Run disabled tests too, in addition to the tests normally being run."); - -GTEST_DEFINE_bool_( - break_on_failure, - internal::BoolFromGTestEnv("break_on_failure", false), - "True iff a failed assertion should be a debugger break-point."); - -GTEST_DEFINE_bool_( - catch_exceptions, - internal::BoolFromGTestEnv("catch_exceptions", true), - "True iff " GTEST_NAME_ - " should catch exceptions and treat them as test failures."); - -GTEST_DEFINE_string_( - color, - internal::StringFromGTestEnv("color", "auto"), - "Whether to use colors in the output. Valid values: yes, no, " - "and auto. 'auto' means to use colors if the output is " - "being sent to a terminal and the TERM environment variable " - "is set to a terminal type that supports colors."); - -GTEST_DEFINE_string_( - filter, - internal::StringFromGTestEnv("filter", GetDefaultFilter()), - "A colon-separated list of glob (not regex) patterns " - "for filtering the tests to run, optionally followed by a " - "'-' and a : separated list of negative patterns (tests to " - "exclude). A test is run if it matches one of the positive " - "patterns and does not match any of the negative patterns."); - -GTEST_DEFINE_bool_( - install_failure_signal_handler, - internal::BoolFromGTestEnv("install_failure_signal_handler", false), - "If true and supported on the current platform, " GTEST_NAME_ " should " - "install a signal handler that dumps debugging information when fatal " - "signals are raised."); - -GTEST_DEFINE_bool_(list_tests, false, - "List all tests without running them."); - -// The net priority order after flag processing is thus: -// --gtest_output command line flag -// GTEST_OUTPUT environment variable -// XML_OUTPUT_FILE environment variable -// '' -GTEST_DEFINE_string_( - output, - internal::StringFromGTestEnv("output", - internal::OutputFlagAlsoCheckEnvVar().c_str()), - "A format (defaults to \"xml\" but can be specified to be \"json\"), " - "optionally followed by a colon and an output file name or directory. " - "A directory is indicated by a trailing pathname separator. " - "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " - "If a directory is specified, output files will be created " - "within that directory, with file-names based on the test " - "executable's name and, if necessary, made unique by adding " - "digits."); - -GTEST_DEFINE_bool_( - print_time, - internal::BoolFromGTestEnv("print_time", true), - "True iff " GTEST_NAME_ - " should display elapsed time in text output."); - -GTEST_DEFINE_bool_( - print_utf8, - internal::BoolFromGTestEnv("print_utf8", true), - "True iff " GTEST_NAME_ - " prints UTF8 characters as text."); - -GTEST_DEFINE_int32_( - random_seed, - internal::Int32FromGTestEnv("random_seed", 0), - "Random number seed to use when shuffling test orders. Must be in range " - "[1, 99999], or 0 to use a seed based on the current time."); - -GTEST_DEFINE_int32_( - repeat, - internal::Int32FromGTestEnv("repeat", 1), - "How many times to repeat each test. Specify a negative number " - "for repeating forever. Useful for shaking out flaky tests."); - -GTEST_DEFINE_bool_( - show_internal_stack_frames, false, - "True iff " GTEST_NAME_ " should include internal stack frames when " - "printing test failure stack traces."); - -GTEST_DEFINE_bool_( - shuffle, - internal::BoolFromGTestEnv("shuffle", false), - "True iff " GTEST_NAME_ - " should randomize tests' order on every run."); - -GTEST_DEFINE_int32_( - stack_trace_depth, - internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth), - "The maximum number of stack frames to print when an " - "assertion fails. The valid range is 0 through 100, inclusive."); - -GTEST_DEFINE_string_( - stream_result_to, - internal::StringFromGTestEnv("stream_result_to", ""), - "This flag specifies the host name and the port number on which to stream " - "test results. Example: \"localhost:555\". The flag is effective only on " - "Linux."); - -GTEST_DEFINE_bool_( - throw_on_failure, - internal::BoolFromGTestEnv("throw_on_failure", false), - "When this flag is specified, a failed assertion will throw an exception " - "if exceptions are enabled or exit the program with a non-zero code " - "otherwise. For use with an external test framework."); - -#if GTEST_USE_OWN_FLAGFILE_FLAG_ -GTEST_DEFINE_string_( - flagfile, - internal::StringFromGTestEnv("flagfile", ""), - "This flag specifies the flagfile to read command-line flags from."); -#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ - -namespace internal { - -// Generates a random number from [0, range), using a Linear -// Congruential Generator (LCG). Crashes if 'range' is 0 or greater -// than kMaxRange. -UInt32 Random::Generate(UInt32 range) { - // These constants are the same as are used in glibc's rand(3). - // Use wider types than necessary to prevent unsigned overflow diagnostics. - state_ = static_cast(1103515245ULL*state_ + 12345U) % kMaxRange; - - GTEST_CHECK_(range > 0) - << "Cannot generate a number in the range [0, 0)."; - GTEST_CHECK_(range <= kMaxRange) - << "Generation of a number in [0, " << range << ") was requested, " - << "but this can only generate numbers in [0, " << kMaxRange << ")."; - - // Converting via modulus introduces a bit of downward bias, but - // it's simple, and a linear congruential generator isn't too good - // to begin with. - return state_ % range; -} - -// GTestIsInitialized() returns true iff the user has initialized -// Google Test. Useful for catching the user mistake of not initializing -// Google Test before calling RUN_ALL_TESTS(). -static bool GTestIsInitialized() { return GetArgvs().size() > 0; } - -// Iterates over a vector of TestSuites, keeping a running sum of the -// results of calling a given int-returning method on each. -// Returns the sum. -static int SumOverTestSuiteList(const std::vector& case_list, - int (TestSuite::*method)() const) { - int sum = 0; - for (size_t i = 0; i < case_list.size(); i++) { - sum += (case_list[i]->*method)(); - } - return sum; -} - -// Returns true iff the test suite passed. -static bool TestSuitePassed(const TestSuite* test_suite) { - return test_suite->should_run() && test_suite->Passed(); -} - -// Returns true iff the test suite failed. -static bool TestSuiteFailed(const TestSuite* test_suite) { - return test_suite->should_run() && test_suite->Failed(); -} - -// Returns true iff test_suite contains at least one test that should -// run. -static bool ShouldRunTestSuite(const TestSuite* test_suite) { - return test_suite->should_run(); -} - -// AssertHelper constructor. -AssertHelper::AssertHelper(TestPartResult::Type type, - const char* file, - int line, - const char* message) - : data_(new AssertHelperData(type, file, line, message)) { -} - -AssertHelper::~AssertHelper() { - delete data_; -} - -// Message assignment, for assertion streaming support. -void AssertHelper::operator=(const Message& message) const { - UnitTest::GetInstance()-> - AddTestPartResult(data_->type, data_->file, data_->line, - AppendUserMessage(data_->message, message), - UnitTest::GetInstance()->impl() - ->CurrentOsStackTraceExceptTop(1) - // Skips the stack frame for this function itself. - ); // NOLINT -} - -// A copy of all command line arguments. Set by InitGoogleTest(). -static ::std::vector g_argvs; - -::std::vector GetArgvs() { -#if defined(GTEST_CUSTOM_GET_ARGVS_) - // GTEST_CUSTOM_GET_ARGVS_() may return a container of std::string or - // ::string. This code converts it to the appropriate type. - const auto& custom = GTEST_CUSTOM_GET_ARGVS_(); - return ::std::vector(custom.begin(), custom.end()); -#else // defined(GTEST_CUSTOM_GET_ARGVS_) - return g_argvs; -#endif // defined(GTEST_CUSTOM_GET_ARGVS_) -} - -// Returns the current application's name, removing directory path if that -// is present. -FilePath GetCurrentExecutableName() { - FilePath result; - -#if GTEST_OS_WINDOWS || GTEST_OS_OS2 - result.Set(FilePath(GetArgvs()[0]).RemoveExtension("exe")); -#else - result.Set(FilePath(GetArgvs()[0])); -#endif // GTEST_OS_WINDOWS - - return result.RemoveDirectoryName(); -} - -// Functions for processing the gtest_output flag. - -// Returns the output format, or "" for normal printed output. -std::string UnitTestOptions::GetOutputFormat() { - const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); - const char* const colon = strchr(gtest_output_flag, ':'); - return (colon == nullptr) - ? std::string(gtest_output_flag) - : std::string(gtest_output_flag, - static_cast(colon - gtest_output_flag)); -} - -// Returns the name of the requested output file, or the default if none -// was explicitly specified. -std::string UnitTestOptions::GetAbsolutePathToOutputFile() { - const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); - - std::string format = GetOutputFormat(); - if (format.empty()) - format = std::string(kDefaultOutputFormat); - - const char* const colon = strchr(gtest_output_flag, ':'); - if (colon == nullptr) - return internal::FilePath::MakeFileName( - internal::FilePath( - UnitTest::GetInstance()->original_working_dir()), - internal::FilePath(kDefaultOutputFile), 0, - format.c_str()).string(); - - internal::FilePath output_name(colon + 1); - if (!output_name.IsAbsolutePath()) - output_name = internal::FilePath::ConcatPaths( - internal::FilePath(UnitTest::GetInstance()->original_working_dir()), - internal::FilePath(colon + 1)); - - if (!output_name.IsDirectory()) - return output_name.string(); - - internal::FilePath result(internal::FilePath::GenerateUniqueFileName( - output_name, internal::GetCurrentExecutableName(), - GetOutputFormat().c_str())); - return result.string(); -} - -// Returns true iff the wildcard pattern matches the string. The -// first ':' or '\0' character in pattern marks the end of it. -// -// This recursive algorithm isn't very efficient, but is clear and -// works well enough for matching test names, which are short. -bool UnitTestOptions::PatternMatchesString(const char *pattern, - const char *str) { - switch (*pattern) { - case '\0': - case ':': // Either ':' or '\0' marks the end of the pattern. - return *str == '\0'; - case '?': // Matches any single character. - return *str != '\0' && PatternMatchesString(pattern + 1, str + 1); - case '*': // Matches any string (possibly empty) of characters. - return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || - PatternMatchesString(pattern + 1, str); - default: // Non-special character. Matches itself. - return *pattern == *str && - PatternMatchesString(pattern + 1, str + 1); - } -} - -bool UnitTestOptions::MatchesFilter( - const std::string& name, const char* filter) { - const char *cur_pattern = filter; - for (;;) { - if (PatternMatchesString(cur_pattern, name.c_str())) { - return true; - } - - // Finds the next pattern in the filter. - cur_pattern = strchr(cur_pattern, ':'); - - // Returns if no more pattern can be found. - if (cur_pattern == nullptr) { - return false; - } - - // Skips the pattern separater (the ':' character). - cur_pattern++; - } -} - -// Returns true iff the user-specified filter matches the test suite -// name and the test name. -bool UnitTestOptions::FilterMatchesTest(const std::string& test_suite_name, - const std::string& test_name) { - const std::string& full_name = test_suite_name + "." + test_name.c_str(); - - // Split --gtest_filter at '-', if there is one, to separate into - // positive filter and negative filter portions - const char* const p = GTEST_FLAG(filter).c_str(); - const char* const dash = strchr(p, '-'); - std::string positive; - std::string negative; - if (dash == nullptr) { - positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter - negative = ""; - } else { - positive = std::string(p, dash); // Everything up to the dash - negative = std::string(dash + 1); // Everything after the dash - if (positive.empty()) { - // Treat '-test1' as the same as '*-test1' - positive = kUniversalFilter; - } - } - - // A filter is a colon-separated list of patterns. It matches a - // test if any pattern in it matches the test. - return (MatchesFilter(full_name, positive.c_str()) && - !MatchesFilter(full_name, negative.c_str())); -} - -#if GTEST_HAS_SEH -// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the -// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. -// This function is useful as an __except condition. -int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { - // Google Test should handle a SEH exception if: - // 1. the user wants it to, AND - // 2. this is not a breakpoint exception, AND - // 3. this is not a C++ exception (VC++ implements them via SEH, - // apparently). - // - // SEH exception code for C++ exceptions. - // (see http://support.microsoft.com/kb/185294 for more information). - const DWORD kCxxExceptionCode = 0xe06d7363; - - bool should_handle = true; - - if (!GTEST_FLAG(catch_exceptions)) - should_handle = false; - else if (exception_code == EXCEPTION_BREAKPOINT) - should_handle = false; - else if (exception_code == kCxxExceptionCode) - should_handle = false; - - return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; -} -#endif // GTEST_HAS_SEH - -} // namespace internal - -// The c'tor sets this object as the test part result reporter used by -// Google Test. The 'result' parameter specifies where to report the -// results. Intercepts only failures from the current thread. -ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( - TestPartResultArray* result) - : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), - result_(result) { - Init(); -} - -// The c'tor sets this object as the test part result reporter used by -// Google Test. The 'result' parameter specifies where to report the -// results. -ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( - InterceptMode intercept_mode, TestPartResultArray* result) - : intercept_mode_(intercept_mode), - result_(result) { - Init(); -} - -void ScopedFakeTestPartResultReporter::Init() { - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - if (intercept_mode_ == INTERCEPT_ALL_THREADS) { - old_reporter_ = impl->GetGlobalTestPartResultReporter(); - impl->SetGlobalTestPartResultReporter(this); - } else { - old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); - impl->SetTestPartResultReporterForCurrentThread(this); - } -} - -// The d'tor restores the test part result reporter used by Google Test -// before. -ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - if (intercept_mode_ == INTERCEPT_ALL_THREADS) { - impl->SetGlobalTestPartResultReporter(old_reporter_); - } else { - impl->SetTestPartResultReporterForCurrentThread(old_reporter_); - } -} - -// Increments the test part result count and remembers the result. -// This method is from the TestPartResultReporterInterface interface. -void ScopedFakeTestPartResultReporter::ReportTestPartResult( - const TestPartResult& result) { - result_->Append(result); -} - -namespace internal { - -// Returns the type ID of ::testing::Test. We should always call this -// instead of GetTypeId< ::testing::Test>() to get the type ID of -// testing::Test. This is to work around a suspected linker bug when -// using Google Test as a framework on Mac OS X. The bug causes -// GetTypeId< ::testing::Test>() to return different values depending -// on whether the call is from the Google Test framework itself or -// from user test code. GetTestTypeId() is guaranteed to always -// return the same value, as it always calls GetTypeId<>() from the -// gtest.cc, which is within the Google Test framework. -TypeId GetTestTypeId() { - return GetTypeId(); -} - -// The value of GetTestTypeId() as seen from within the Google Test -// library. This is solely for testing GetTestTypeId(). -extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); - -// This predicate-formatter checks that 'results' contains a test part -// failure of the given type and that the failure message contains the -// given substring. -static AssertionResult HasOneFailure(const char* /* results_expr */, - const char* /* type_expr */, - const char* /* substr_expr */, - const TestPartResultArray& results, - TestPartResult::Type type, - const std::string& substr) { - const std::string expected(type == TestPartResult::kFatalFailure ? - "1 fatal failure" : - "1 non-fatal failure"); - Message msg; - if (results.size() != 1) { - msg << "Expected: " << expected << "\n" - << " Actual: " << results.size() << " failures"; - for (int i = 0; i < results.size(); i++) { - msg << "\n" << results.GetTestPartResult(i); - } - return AssertionFailure() << msg; - } - - const TestPartResult& r = results.GetTestPartResult(0); - if (r.type() != type) { - return AssertionFailure() << "Expected: " << expected << "\n" - << " Actual:\n" - << r; - } - - if (strstr(r.message(), substr.c_str()) == nullptr) { - return AssertionFailure() << "Expected: " << expected << " containing \"" - << substr << "\"\n" - << " Actual:\n" - << r; - } - - return AssertionSuccess(); -} - -// The constructor of SingleFailureChecker remembers where to look up -// test part results, what type of failure we expect, and what -// substring the failure message should contain. -SingleFailureChecker::SingleFailureChecker(const TestPartResultArray* results, - TestPartResult::Type type, - const std::string& substr) - : results_(results), type_(type), substr_(substr) {} - -// The destructor of SingleFailureChecker verifies that the given -// TestPartResultArray contains exactly one failure that has the given -// type and contains the given substring. If that's not the case, a -// non-fatal failure will be generated. -SingleFailureChecker::~SingleFailureChecker() { - EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); -} - -DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( - UnitTestImpl* unit_test) : unit_test_(unit_test) {} - -void DefaultGlobalTestPartResultReporter::ReportTestPartResult( - const TestPartResult& result) { - unit_test_->current_test_result()->AddTestPartResult(result); - unit_test_->listeners()->repeater()->OnTestPartResult(result); -} - -DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( - UnitTestImpl* unit_test) : unit_test_(unit_test) {} - -void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( - const TestPartResult& result) { - unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); -} - -// Returns the global test part result reporter. -TestPartResultReporterInterface* -UnitTestImpl::GetGlobalTestPartResultReporter() { - internal::MutexLock lock(&global_test_part_result_reporter_mutex_); - return global_test_part_result_repoter_; -} - -// Sets the global test part result reporter. -void UnitTestImpl::SetGlobalTestPartResultReporter( - TestPartResultReporterInterface* reporter) { - internal::MutexLock lock(&global_test_part_result_reporter_mutex_); - global_test_part_result_repoter_ = reporter; -} - -// Returns the test part result reporter for the current thread. -TestPartResultReporterInterface* -UnitTestImpl::GetTestPartResultReporterForCurrentThread() { - return per_thread_test_part_result_reporter_.get(); -} - -// Sets the test part result reporter for the current thread. -void UnitTestImpl::SetTestPartResultReporterForCurrentThread( - TestPartResultReporterInterface* reporter) { - per_thread_test_part_result_reporter_.set(reporter); -} - -// Gets the number of successful test suites. -int UnitTestImpl::successful_test_suite_count() const { - return CountIf(test_suites_, TestSuitePassed); -} - -// Gets the number of failed test suites. -int UnitTestImpl::failed_test_suite_count() const { - return CountIf(test_suites_, TestSuiteFailed); -} - -// Gets the number of all test suites. -int UnitTestImpl::total_test_suite_count() const { - return static_cast(test_suites_.size()); -} - -// Gets the number of all test suites that contain at least one test -// that should run. -int UnitTestImpl::test_suite_to_run_count() const { - return CountIf(test_suites_, ShouldRunTestSuite); -} - -// Gets the number of successful tests. -int UnitTestImpl::successful_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::successful_test_count); -} - -// Gets the number of skipped tests. -int UnitTestImpl::skipped_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::skipped_test_count); -} - -// Gets the number of failed tests. -int UnitTestImpl::failed_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::failed_test_count); -} - -// Gets the number of disabled tests that will be reported in the XML report. -int UnitTestImpl::reportable_disabled_test_count() const { - return SumOverTestSuiteList(test_suites_, - &TestSuite::reportable_disabled_test_count); -} - -// Gets the number of disabled tests. -int UnitTestImpl::disabled_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::disabled_test_count); -} - -// Gets the number of tests to be printed in the XML report. -int UnitTestImpl::reportable_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::reportable_test_count); -} - -// Gets the number of all tests. -int UnitTestImpl::total_test_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::total_test_count); -} - -// Gets the number of tests that should run. -int UnitTestImpl::test_to_run_count() const { - return SumOverTestSuiteList(test_suites_, &TestSuite::test_to_run_count); -} - -// Returns the current OS stack trace as an std::string. -// -// The maximum number of stack frames to be included is specified by -// the gtest_stack_trace_depth flag. The skip_count parameter -// specifies the number of top frames to be skipped, which doesn't -// count against the number of frames to be included. -// -// For example, if Foo() calls Bar(), which in turn calls -// CurrentOsStackTraceExceptTop(1), Foo() will be included in the -// trace but Bar() and CurrentOsStackTraceExceptTop() won't. -std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { - return os_stack_trace_getter()->CurrentStackTrace( - static_cast(GTEST_FLAG(stack_trace_depth)), - skip_count + 1 - // Skips the user-specified number of frames plus this function - // itself. - ); // NOLINT -} - -// Returns the current time in milliseconds. -TimeInMillis GetTimeInMillis() { -#if GTEST_OS_WINDOWS_MOBILE || defined(__BORLANDC__) - // Difference between 1970-01-01 and 1601-01-01 in milliseconds. - // http://analogous.blogspot.com/2005/04/epoch.html - const TimeInMillis kJavaEpochToWinFileTimeDelta = - static_cast(116444736UL) * 100000UL; - const DWORD kTenthMicrosInMilliSecond = 10000; - - SYSTEMTIME now_systime; - FILETIME now_filetime; - ULARGE_INTEGER now_int64; - GetSystemTime(&now_systime); - if (SystemTimeToFileTime(&now_systime, &now_filetime)) { - now_int64.LowPart = now_filetime.dwLowDateTime; - now_int64.HighPart = now_filetime.dwHighDateTime; - now_int64.QuadPart = (now_int64.QuadPart / kTenthMicrosInMilliSecond) - - kJavaEpochToWinFileTimeDelta; - return now_int64.QuadPart; - } - return 0; -#elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ - __timeb64 now; - - // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 - // (deprecated function) there. - GTEST_DISABLE_MSC_DEPRECATED_PUSH_() - _ftime64(&now); - GTEST_DISABLE_MSC_DEPRECATED_POP_() - - return static_cast(now.time) * 1000 + now.millitm; -#elif GTEST_HAS_GETTIMEOFDAY_ - struct timeval now; - gettimeofday(&now, nullptr); - return static_cast(now.tv_sec) * 1000 + now.tv_usec / 1000; -#else -# error "Don't know how to get the current time on your system." -#endif -} - -// Utilities - -// class String. - -#if GTEST_OS_WINDOWS_MOBILE -// Creates a UTF-16 wide string from the given ANSI string, allocating -// memory using new. The caller is responsible for deleting the return -// value using delete[]. Returns the wide string, or NULL if the -// input is NULL. -LPCWSTR String::AnsiToUtf16(const char* ansi) { - if (!ansi) return nullptr; - const int length = strlen(ansi); - const int unicode_length = - MultiByteToWideChar(CP_ACP, 0, ansi, length, nullptr, 0); - WCHAR* unicode = new WCHAR[unicode_length + 1]; - MultiByteToWideChar(CP_ACP, 0, ansi, length, - unicode, unicode_length); - unicode[unicode_length] = 0; - return unicode; -} - -// Creates an ANSI string from the given wide string, allocating -// memory using new. The caller is responsible for deleting the return -// value using delete[]. Returns the ANSI string, or NULL if the -// input is NULL. -const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { - if (!utf16_str) return nullptr; - const int ansi_length = WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, nullptr, - 0, nullptr, nullptr); - char* ansi = new char[ansi_length + 1]; - WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, ansi, ansi_length, nullptr, - nullptr); - ansi[ansi_length] = 0; - return ansi; -} - -#endif // GTEST_OS_WINDOWS_MOBILE - -// Compares two C strings. Returns true iff they have the same content. -// -// Unlike strcmp(), this function can handle NULL argument(s). A NULL -// C string is considered different to any non-NULL C string, -// including the empty string. -bool String::CStringEquals(const char * lhs, const char * rhs) { - if (lhs == nullptr) return rhs == nullptr; - - if (rhs == nullptr) return false; - - return strcmp(lhs, rhs) == 0; -} - -#if GTEST_HAS_STD_WSTRING - -// Converts an array of wide chars to a narrow string using the UTF-8 -// encoding, and streams the result to the given Message object. -static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, - Message* msg) { - for (size_t i = 0; i != length; ) { // NOLINT - if (wstr[i] != L'\0') { - *msg << WideStringToUtf8(wstr + i, static_cast(length - i)); - while (i != length && wstr[i] != L'\0') - i++; - } else { - *msg << '\0'; - i++; - } - } -} - -#endif // GTEST_HAS_STD_WSTRING - -void SplitString(const ::std::string& str, char delimiter, - ::std::vector< ::std::string>* dest) { - ::std::vector< ::std::string> parsed; - ::std::string::size_type pos = 0; - while (::testing::internal::AlwaysTrue()) { - const ::std::string::size_type colon = str.find(delimiter, pos); - if (colon == ::std::string::npos) { - parsed.push_back(str.substr(pos)); - break; - } else { - parsed.push_back(str.substr(pos, colon - pos)); - pos = colon + 1; - } - } - dest->swap(parsed); -} - -} // namespace internal - -// Constructs an empty Message. -// We allocate the stringstream separately because otherwise each use of -// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's -// stack frame leading to huge stack frames in some cases; gcc does not reuse -// the stack space. -Message::Message() : ss_(new ::std::stringstream) { - // By default, we want there to be enough precision when printing - // a double to a Message. - *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); -} - -// These two overloads allow streaming a wide C string to a Message -// using the UTF-8 encoding. -Message& Message::operator <<(const wchar_t* wide_c_str) { - return *this << internal::String::ShowWideCString(wide_c_str); -} -Message& Message::operator <<(wchar_t* wide_c_str) { - return *this << internal::String::ShowWideCString(wide_c_str); -} - -#if GTEST_HAS_STD_WSTRING -// Converts the given wide string to a narrow string using the UTF-8 -// encoding, and streams the result to this Message object. -Message& Message::operator <<(const ::std::wstring& wstr) { - internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); - return *this; -} -#endif // GTEST_HAS_STD_WSTRING - -// Gets the text streamed to this object so far as an std::string. -// Each '\0' character in the buffer is replaced with "\\0". -std::string Message::GetString() const { - return internal::StringStreamToString(ss_.get()); -} - -// AssertionResult constructors. -// Used in EXPECT_TRUE/FALSE(assertion_result). -AssertionResult::AssertionResult(const AssertionResult& other) - : success_(other.success_), - message_(other.message_.get() != nullptr - ? new ::std::string(*other.message_) - : static_cast< ::std::string*>(nullptr)) {} - -// Swaps two AssertionResults. -void AssertionResult::swap(AssertionResult& other) { - using std::swap; - swap(success_, other.success_); - swap(message_, other.message_); -} - -// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. -AssertionResult AssertionResult::operator!() const { - AssertionResult negation(!success_); - if (message_.get() != nullptr) negation << *message_; - return negation; -} - -// Makes a successful assertion result. -AssertionResult AssertionSuccess() { - return AssertionResult(true); -} - -// Makes a failed assertion result. -AssertionResult AssertionFailure() { - return AssertionResult(false); -} - -// Makes a failed assertion result with the given failure message. -// Deprecated; use AssertionFailure() << message. -AssertionResult AssertionFailure(const Message& message) { - return AssertionFailure() << message; -} - -namespace internal { - -namespace edit_distance { -std::vector CalculateOptimalEdits(const std::vector& left, - const std::vector& right) { - std::vector > costs( - left.size() + 1, std::vector(right.size() + 1)); - std::vector > best_move( - left.size() + 1, std::vector(right.size() + 1)); - - // Populate for empty right. - for (size_t l_i = 0; l_i < costs.size(); ++l_i) { - costs[l_i][0] = static_cast(l_i); - best_move[l_i][0] = kRemove; - } - // Populate for empty left. - for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) { - costs[0][r_i] = static_cast(r_i); - best_move[0][r_i] = kAdd; - } - - for (size_t l_i = 0; l_i < left.size(); ++l_i) { - for (size_t r_i = 0; r_i < right.size(); ++r_i) { - if (left[l_i] == right[r_i]) { - // Found a match. Consume it. - costs[l_i + 1][r_i + 1] = costs[l_i][r_i]; - best_move[l_i + 1][r_i + 1] = kMatch; - continue; - } - - const double add = costs[l_i + 1][r_i]; - const double remove = costs[l_i][r_i + 1]; - const double replace = costs[l_i][r_i]; - if (add < remove && add < replace) { - costs[l_i + 1][r_i + 1] = add + 1; - best_move[l_i + 1][r_i + 1] = kAdd; - } else if (remove < add && remove < replace) { - costs[l_i + 1][r_i + 1] = remove + 1; - best_move[l_i + 1][r_i + 1] = kRemove; - } else { - // We make replace a little more expensive than add/remove to lower - // their priority. - costs[l_i + 1][r_i + 1] = replace + 1.00001; - best_move[l_i + 1][r_i + 1] = kReplace; - } - } - } - - // Reconstruct the best path. We do it in reverse order. - std::vector best_path; - for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) { - EditType move = best_move[l_i][r_i]; - best_path.push_back(move); - l_i -= move != kAdd; - r_i -= move != kRemove; - } - std::reverse(best_path.begin(), best_path.end()); - return best_path; -} - -namespace { - -// Helper class to convert string into ids with deduplication. -class InternalStrings { - public: - size_t GetId(const std::string& str) { - IdMap::iterator it = ids_.find(str); - if (it != ids_.end()) return it->second; - size_t id = ids_.size(); - return ids_[str] = id; - } - - private: - typedef std::map IdMap; - IdMap ids_; -}; - -} // namespace - -std::vector CalculateOptimalEdits( - const std::vector& left, - const std::vector& right) { - std::vector left_ids, right_ids; - { - InternalStrings intern_table; - for (size_t i = 0; i < left.size(); ++i) { - left_ids.push_back(intern_table.GetId(left[i])); - } - for (size_t i = 0; i < right.size(); ++i) { - right_ids.push_back(intern_table.GetId(right[i])); - } - } - return CalculateOptimalEdits(left_ids, right_ids); -} - -namespace { - -// Helper class that holds the state for one hunk and prints it out to the -// stream. -// It reorders adds/removes when possible to group all removes before all -// adds. It also adds the hunk header before printint into the stream. -class Hunk { - public: - Hunk(size_t left_start, size_t right_start) - : left_start_(left_start), - right_start_(right_start), - adds_(), - removes_(), - common_() {} - - void PushLine(char edit, const char* line) { - switch (edit) { - case ' ': - ++common_; - FlushEdits(); - hunk_.push_back(std::make_pair(' ', line)); - break; - case '-': - ++removes_; - hunk_removes_.push_back(std::make_pair('-', line)); - break; - case '+': - ++adds_; - hunk_adds_.push_back(std::make_pair('+', line)); - break; - } - } - - void PrintTo(std::ostream* os) { - PrintHeader(os); - FlushEdits(); - for (std::list >::const_iterator it = - hunk_.begin(); - it != hunk_.end(); ++it) { - *os << it->first << it->second << "\n"; - } - } - - bool has_edits() const { return adds_ || removes_; } - - private: - void FlushEdits() { - hunk_.splice(hunk_.end(), hunk_removes_); - hunk_.splice(hunk_.end(), hunk_adds_); - } - - // Print a unified diff header for one hunk. - // The format is - // "@@ -, +, @@" - // where the left/right parts are omitted if unnecessary. - void PrintHeader(std::ostream* ss) const { - *ss << "@@ "; - if (removes_) { - *ss << "-" << left_start_ << "," << (removes_ + common_); - } - if (removes_ && adds_) { - *ss << " "; - } - if (adds_) { - *ss << "+" << right_start_ << "," << (adds_ + common_); - } - *ss << " @@\n"; - } - - size_t left_start_, right_start_; - size_t adds_, removes_, common_; - std::list > hunk_, hunk_adds_, hunk_removes_; -}; - -} // namespace - -// Create a list of diff hunks in Unified diff format. -// Each hunk has a header generated by PrintHeader above plus a body with -// lines prefixed with ' ' for no change, '-' for deletion and '+' for -// addition. -// 'context' represents the desired unchanged prefix/suffix around the diff. -// If two hunks are close enough that their contexts overlap, then they are -// joined into one hunk. -std::string CreateUnifiedDiff(const std::vector& left, - const std::vector& right, - size_t context) { - const std::vector edits = CalculateOptimalEdits(left, right); - - size_t l_i = 0, r_i = 0, edit_i = 0; - std::stringstream ss; - while (edit_i < edits.size()) { - // Find first edit. - while (edit_i < edits.size() && edits[edit_i] == kMatch) { - ++l_i; - ++r_i; - ++edit_i; - } - - // Find the first line to include in the hunk. - const size_t prefix_context = std::min(l_i, context); - Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1); - for (size_t i = prefix_context; i > 0; --i) { - hunk.PushLine(' ', left[l_i - i].c_str()); - } - - // Iterate the edits until we found enough suffix for the hunk or the input - // is over. - size_t n_suffix = 0; - for (; edit_i < edits.size(); ++edit_i) { - if (n_suffix >= context) { - // Continue only if the next hunk is very close. - auto it = edits.begin() + static_cast(edit_i); - while (it != edits.end() && *it == kMatch) ++it; - if (it == edits.end() || - static_cast(it - edits.begin()) - edit_i >= context) { - // There is no next edit or it is too far away. - break; - } - } - - EditType edit = edits[edit_i]; - // Reset count when a non match is found. - n_suffix = edit == kMatch ? n_suffix + 1 : 0; - - if (edit == kMatch || edit == kRemove || edit == kReplace) { - hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str()); - } - if (edit == kAdd || edit == kReplace) { - hunk.PushLine('+', right[r_i].c_str()); - } - - // Advance indices, depending on edit type. - l_i += edit != kAdd; - r_i += edit != kRemove; - } - - if (!hunk.has_edits()) { - // We are done. We don't want this hunk. - break; - } - - hunk.PrintTo(&ss); - } - return ss.str(); -} - -} // namespace edit_distance - -namespace { - -// The string representation of the values received in EqFailure() are already -// escaped. Split them on escaped '\n' boundaries. Leave all other escaped -// characters the same. -std::vector SplitEscapedString(const std::string& str) { - std::vector lines; - size_t start = 0, end = str.size(); - if (end > 2 && str[0] == '"' && str[end - 1] == '"') { - ++start; - --end; - } - bool escaped = false; - for (size_t i = start; i + 1 < end; ++i) { - if (escaped) { - escaped = false; - if (str[i] == 'n') { - lines.push_back(str.substr(start, i - start - 1)); - start = i + 1; - } - } else { - escaped = str[i] == '\\'; - } - } - lines.push_back(str.substr(start, end - start)); - return lines; -} - -} // namespace - -// Constructs and returns the message for an equality assertion -// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. -// -// The first four parameters are the expressions used in the assertion -// and their values, as strings. For example, for ASSERT_EQ(foo, bar) -// where foo is 5 and bar is 6, we have: -// -// lhs_expression: "foo" -// rhs_expression: "bar" -// lhs_value: "5" -// rhs_value: "6" -// -// The ignoring_case parameter is true iff the assertion is a -// *_STRCASEEQ*. When it's true, the string "Ignoring case" will -// be inserted into the message. -AssertionResult EqFailure(const char* lhs_expression, - const char* rhs_expression, - const std::string& lhs_value, - const std::string& rhs_value, - bool ignoring_case) { - Message msg; - msg << "Expected equality of these values:"; - msg << "\n " << lhs_expression; - if (lhs_value != lhs_expression) { - msg << "\n Which is: " << lhs_value; - } - msg << "\n " << rhs_expression; - if (rhs_value != rhs_expression) { - msg << "\n Which is: " << rhs_value; - } - - if (ignoring_case) { - msg << "\nIgnoring case"; - } - - if (!lhs_value.empty() && !rhs_value.empty()) { - const std::vector lhs_lines = - SplitEscapedString(lhs_value); - const std::vector rhs_lines = - SplitEscapedString(rhs_value); - if (lhs_lines.size() > 1 || rhs_lines.size() > 1) { - msg << "\nWith diff:\n" - << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines); - } - } - - return AssertionFailure() << msg; -} - -// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. -std::string GetBoolAssertionFailureMessage( - const AssertionResult& assertion_result, - const char* expression_text, - const char* actual_predicate_value, - const char* expected_predicate_value) { - const char* actual_message = assertion_result.message(); - Message msg; - msg << "Value of: " << expression_text - << "\n Actual: " << actual_predicate_value; - if (actual_message[0] != '\0') - msg << " (" << actual_message << ")"; - msg << "\nExpected: " << expected_predicate_value; - return msg.GetString(); -} - -// Helper function for implementing ASSERT_NEAR. -AssertionResult DoubleNearPredFormat(const char* expr1, - const char* expr2, - const char* abs_error_expr, - double val1, - double val2, - double abs_error) { - const double diff = fabs(val1 - val2); - if (diff <= abs_error) return AssertionSuccess(); - - return AssertionFailure() - << "The difference between " << expr1 << " and " << expr2 - << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" - << expr1 << " evaluates to " << val1 << ",\n" - << expr2 << " evaluates to " << val2 << ", and\n" - << abs_error_expr << " evaluates to " << abs_error << "."; -} - - -// Helper template for implementing FloatLE() and DoubleLE(). -template -AssertionResult FloatingPointLE(const char* expr1, - const char* expr2, - RawType val1, - RawType val2) { - // Returns success if val1 is less than val2, - if (val1 < val2) { - return AssertionSuccess(); - } - - // or if val1 is almost equal to val2. - const FloatingPoint lhs(val1), rhs(val2); - if (lhs.AlmostEquals(rhs)) { - return AssertionSuccess(); - } - - // Note that the above two checks will both fail if either val1 or - // val2 is NaN, as the IEEE floating-point standard requires that - // any predicate involving a NaN must return false. - - ::std::stringstream val1_ss; - val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << val1; - - ::std::stringstream val2_ss; - val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << val2; - - return AssertionFailure() - << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" - << " Actual: " << StringStreamToString(&val1_ss) << " vs " - << StringStreamToString(&val2_ss); -} - -} // namespace internal - -// Asserts that val1 is less than, or almost equal to, val2. Fails -// otherwise. In particular, it fails if either val1 or val2 is NaN. -AssertionResult FloatLE(const char* expr1, const char* expr2, - float val1, float val2) { - return internal::FloatingPointLE(expr1, expr2, val1, val2); -} - -// Asserts that val1 is less than, or almost equal to, val2. Fails -// otherwise. In particular, it fails if either val1 or val2 is NaN. -AssertionResult DoubleLE(const char* expr1, const char* expr2, - double val1, double val2) { - return internal::FloatingPointLE(expr1, expr2, val1, val2); -} - -namespace internal { - -// The helper function for {ASSERT|EXPECT}_EQ with int or enum -// arguments. -AssertionResult CmpHelperEQ(const char* lhs_expression, - const char* rhs_expression, - BiggestInt lhs, - BiggestInt rhs) { - if (lhs == rhs) { - return AssertionSuccess(); - } - - return EqFailure(lhs_expression, - rhs_expression, - FormatForComparisonFailureMessage(lhs, rhs), - FormatForComparisonFailureMessage(rhs, lhs), - false); -} - -// A macro for implementing the helper functions needed to implement -// ASSERT_?? and EXPECT_?? with integer or enum arguments. It is here -// just to avoid copy-and-paste of similar code. -#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ -AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ - BiggestInt val1, BiggestInt val2) {\ - if (val1 op val2) {\ - return AssertionSuccess();\ - } else {\ - return AssertionFailure() \ - << "Expected: (" << expr1 << ") " #op " (" << expr2\ - << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ - << " vs " << FormatForComparisonFailureMessage(val2, val1);\ - }\ -} - -// Implements the helper function for {ASSERT|EXPECT}_NE with int or -// enum arguments. -GTEST_IMPL_CMP_HELPER_(NE, !=) -// Implements the helper function for {ASSERT|EXPECT}_LE with int or -// enum arguments. -GTEST_IMPL_CMP_HELPER_(LE, <=) -// Implements the helper function for {ASSERT|EXPECT}_LT with int or -// enum arguments. -GTEST_IMPL_CMP_HELPER_(LT, < ) -// Implements the helper function for {ASSERT|EXPECT}_GE with int or -// enum arguments. -GTEST_IMPL_CMP_HELPER_(GE, >=) -// Implements the helper function for {ASSERT|EXPECT}_GT with int or -// enum arguments. -GTEST_IMPL_CMP_HELPER_(GT, > ) - -#undef GTEST_IMPL_CMP_HELPER_ - -// The helper function for {ASSERT|EXPECT}_STREQ. -AssertionResult CmpHelperSTREQ(const char* lhs_expression, - const char* rhs_expression, - const char* lhs, - const char* rhs) { - if (String::CStringEquals(lhs, rhs)) { - return AssertionSuccess(); - } - - return EqFailure(lhs_expression, - rhs_expression, - PrintToString(lhs), - PrintToString(rhs), - false); -} - -// The helper function for {ASSERT|EXPECT}_STRCASEEQ. -AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression, - const char* rhs_expression, - const char* lhs, - const char* rhs) { - if (String::CaseInsensitiveCStringEquals(lhs, rhs)) { - return AssertionSuccess(); - } - - return EqFailure(lhs_expression, - rhs_expression, - PrintToString(lhs), - PrintToString(rhs), - true); -} - -// The helper function for {ASSERT|EXPECT}_STRNE. -AssertionResult CmpHelperSTRNE(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2) { - if (!String::CStringEquals(s1, s2)) { - return AssertionSuccess(); - } else { - return AssertionFailure() << "Expected: (" << s1_expression << ") != (" - << s2_expression << "), actual: \"" - << s1 << "\" vs \"" << s2 << "\""; - } -} - -// The helper function for {ASSERT|EXPECT}_STRCASENE. -AssertionResult CmpHelperSTRCASENE(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2) { - if (!String::CaseInsensitiveCStringEquals(s1, s2)) { - return AssertionSuccess(); - } else { - return AssertionFailure() - << "Expected: (" << s1_expression << ") != (" - << s2_expression << ") (ignoring case), actual: \"" - << s1 << "\" vs \"" << s2 << "\""; - } -} - -} // namespace internal - -namespace { - -// Helper functions for implementing IsSubString() and IsNotSubstring(). - -// This group of overloaded functions return true iff needle is a -// substring of haystack. NULL is considered a substring of itself -// only. - -bool IsSubstringPred(const char* needle, const char* haystack) { - if (needle == nullptr || haystack == nullptr) return needle == haystack; - - return strstr(haystack, needle) != nullptr; -} - -bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { - if (needle == nullptr || haystack == nullptr) return needle == haystack; - - return wcsstr(haystack, needle) != nullptr; -} - -// StringType here can be either ::std::string or ::std::wstring. -template -bool IsSubstringPred(const StringType& needle, - const StringType& haystack) { - return haystack.find(needle) != StringType::npos; -} - -// This function implements either IsSubstring() or IsNotSubstring(), -// depending on the value of the expected_to_be_substring parameter. -// StringType here can be const char*, const wchar_t*, ::std::string, -// or ::std::wstring. -template -AssertionResult IsSubstringImpl( - bool expected_to_be_substring, - const char* needle_expr, const char* haystack_expr, - const StringType& needle, const StringType& haystack) { - if (IsSubstringPred(needle, haystack) == expected_to_be_substring) - return AssertionSuccess(); - - const bool is_wide_string = sizeof(needle[0]) > 1; - const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; - return AssertionFailure() - << "Value of: " << needle_expr << "\n" - << " Actual: " << begin_string_quote << needle << "\"\n" - << "Expected: " << (expected_to_be_substring ? "" : "not ") - << "a substring of " << haystack_expr << "\n" - << "Which is: " << begin_string_quote << haystack << "\""; -} - -} // namespace - -// IsSubstring() and IsNotSubstring() check whether needle is a -// substring of haystack (NULL is considered a substring of itself -// only), and return an appropriate error message when they fail. - -AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const char* needle, const char* haystack) { - return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const wchar_t* needle, const wchar_t* haystack) { - return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const char* needle, const char* haystack) { - return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const wchar_t* needle, const wchar_t* haystack) { - return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::string& needle, const ::std::string& haystack) { - return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::string& needle, const ::std::string& haystack) { - return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); -} - -#if GTEST_HAS_STD_WSTRING -AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::wstring& needle, const ::std::wstring& haystack) { - return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); -} - -AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::wstring& needle, const ::std::wstring& haystack) { - return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); -} -#endif // GTEST_HAS_STD_WSTRING - -namespace internal { - -#if GTEST_OS_WINDOWS - -namespace { - -// Helper function for IsHRESULT{SuccessFailure} predicates -AssertionResult HRESULTFailureHelper(const char* expr, - const char* expected, - long hr) { // NOLINT -# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_TV_TITLE - - // Windows CE doesn't support FormatMessage. - const char error_text[] = ""; - -# else - - // Looks up the human-readable system message for the HRESULT code - // and since we're not passing any params to FormatMessage, we don't - // want inserts expanded. - const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS; - const DWORD kBufSize = 4096; - // Gets the system's human readable message string for this HRESULT. - char error_text[kBufSize] = { '\0' }; - DWORD message_length = ::FormatMessageA(kFlags, - 0, // no source, we're asking system - static_cast(hr), // the error - 0, // no line width restrictions - error_text, // output buffer - kBufSize, // buf size - nullptr); // no arguments for inserts - // Trims tailing white space (FormatMessage leaves a trailing CR-LF) - for (; message_length && IsSpace(error_text[message_length - 1]); - --message_length) { - error_text[message_length - 1] = '\0'; - } - -# endif // GTEST_OS_WINDOWS_MOBILE - - const std::string error_hex("0x" + String::FormatHexInt(hr)); - return ::testing::AssertionFailure() - << "Expected: " << expr << " " << expected << ".\n" - << " Actual: " << error_hex << " " << error_text << "\n"; -} - -} // namespace - -AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT - if (SUCCEEDED(hr)) { - return AssertionSuccess(); - } - return HRESULTFailureHelper(expr, "succeeds", hr); -} - -AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT - if (FAILED(hr)) { - return AssertionSuccess(); - } - return HRESULTFailureHelper(expr, "fails", hr); -} - -#endif // GTEST_OS_WINDOWS - -// Utility functions for encoding Unicode text (wide strings) in -// UTF-8. - -// A Unicode code-point can have up to 21 bits, and is encoded in UTF-8 -// like this: -// -// Code-point length Encoding -// 0 - 7 bits 0xxxxxxx -// 8 - 11 bits 110xxxxx 10xxxxxx -// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx -// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - -// The maximum code-point a one-byte UTF-8 sequence can represent. -const UInt32 kMaxCodePoint1 = (static_cast(1) << 7) - 1; - -// The maximum code-point a two-byte UTF-8 sequence can represent. -const UInt32 kMaxCodePoint2 = (static_cast(1) << (5 + 6)) - 1; - -// The maximum code-point a three-byte UTF-8 sequence can represent. -const UInt32 kMaxCodePoint3 = (static_cast(1) << (4 + 2*6)) - 1; - -// The maximum code-point a four-byte UTF-8 sequence can represent. -const UInt32 kMaxCodePoint4 = (static_cast(1) << (3 + 3*6)) - 1; - -// Chops off the n lowest bits from a bit pattern. Returns the n -// lowest bits. As a side effect, the original bit pattern will be -// shifted to the right by n bits. -inline UInt32 ChopLowBits(UInt32* bits, int n) { - const UInt32 low_bits = *bits & ((static_cast(1) << n) - 1); - *bits >>= n; - return low_bits; -} - -// Converts a Unicode code point to a narrow string in UTF-8 encoding. -// code_point parameter is of type UInt32 because wchar_t may not be -// wide enough to contain a code point. -// If the code_point is not a valid Unicode code point -// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted -// to "(Invalid Unicode 0xXXXXXXXX)". -std::string CodePointToUtf8(UInt32 code_point) { - if (code_point > kMaxCodePoint4) { - return "(Invalid Unicode 0x" + String::FormatHexUInt32(code_point) + ")"; - } - - char str[5]; // Big enough for the largest valid code point. - if (code_point <= kMaxCodePoint1) { - str[1] = '\0'; - str[0] = static_cast(code_point); // 0xxxxxxx - } else if (code_point <= kMaxCodePoint2) { - str[2] = '\0'; - str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[0] = static_cast(0xC0 | code_point); // 110xxxxx - } else if (code_point <= kMaxCodePoint3) { - str[3] = '\0'; - str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[0] = static_cast(0xE0 | code_point); // 1110xxxx - } else { // code_point <= kMaxCodePoint4 - str[4] = '\0'; - str[3] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx - str[0] = static_cast(0xF0 | code_point); // 11110xxx - } - return str; -} - -// The following two functions only make sense if the system -// uses UTF-16 for wide string encoding. All supported systems -// with 16 bit wchar_t (Windows, Cygwin) do use UTF-16. - -// Determines if the arguments constitute UTF-16 surrogate pair -// and thus should be combined into a single Unicode code point -// using CreateCodePointFromUtf16SurrogatePair. -inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { - return sizeof(wchar_t) == 2 && - (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00; -} - -// Creates a Unicode code point from UTF16 surrogate pair. -inline UInt32 CreateCodePointFromUtf16SurrogatePair(wchar_t first, - wchar_t second) { - const auto first_u = static_cast(first); - const auto second_u = static_cast(second); - const UInt32 mask = (1 << 10) - 1; - return (sizeof(wchar_t) == 2) - ? (((first_u & mask) << 10) | (second_u & mask)) + 0x10000 - : - // This function should not be called when the condition is - // false, but we provide a sensible default in case it is. - first_u; -} - -// Converts a wide string to a narrow string in UTF-8 encoding. -// The wide string is assumed to have the following encoding: -// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin) -// UTF-32 if sizeof(wchar_t) == 4 (on Linux) -// Parameter str points to a null-terminated wide string. -// Parameter num_chars may additionally limit the number -// of wchar_t characters processed. -1 is used when the entire string -// should be processed. -// If the string contains code points that are not valid Unicode code points -// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output -// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding -// and contains invalid UTF-16 surrogate pairs, values in those pairs -// will be encoded as individual Unicode characters from Basic Normal Plane. -std::string WideStringToUtf8(const wchar_t* str, int num_chars) { - if (num_chars == -1) - num_chars = static_cast(wcslen(str)); - - ::std::stringstream stream; - for (int i = 0; i < num_chars; ++i) { - UInt32 unicode_code_point; - - if (str[i] == L'\0') { - break; - } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { - unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i], - str[i + 1]); - i++; - } else { - unicode_code_point = static_cast(str[i]); - } - - stream << CodePointToUtf8(unicode_code_point); - } - return StringStreamToString(&stream); -} - -// Converts a wide C string to an std::string using the UTF-8 encoding. -// NULL will be converted to "(null)". -std::string String::ShowWideCString(const wchar_t * wide_c_str) { - if (wide_c_str == nullptr) return "(null)"; - - return internal::WideStringToUtf8(wide_c_str, -1); -} - -// Compares two wide C strings. Returns true iff they have the same -// content. -// -// Unlike wcscmp(), this function can handle NULL argument(s). A NULL -// C string is considered different to any non-NULL C string, -// including the empty string. -bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { - if (lhs == nullptr) return rhs == nullptr; - - if (rhs == nullptr) return false; - - return wcscmp(lhs, rhs) == 0; -} - -// Helper function for *_STREQ on wide strings. -AssertionResult CmpHelperSTREQ(const char* lhs_expression, - const char* rhs_expression, - const wchar_t* lhs, - const wchar_t* rhs) { - if (String::WideCStringEquals(lhs, rhs)) { - return AssertionSuccess(); - } - - return EqFailure(lhs_expression, - rhs_expression, - PrintToString(lhs), - PrintToString(rhs), - false); -} - -// Helper function for *_STRNE on wide strings. -AssertionResult CmpHelperSTRNE(const char* s1_expression, - const char* s2_expression, - const wchar_t* s1, - const wchar_t* s2) { - if (!String::WideCStringEquals(s1, s2)) { - return AssertionSuccess(); - } - - return AssertionFailure() << "Expected: (" << s1_expression << ") != (" - << s2_expression << "), actual: " - << PrintToString(s1) - << " vs " << PrintToString(s2); -} - -// Compares two C strings, ignoring case. Returns true iff they have -// the same content. -// -// Unlike strcasecmp(), this function can handle NULL argument(s). A -// NULL C string is considered different to any non-NULL C string, -// including the empty string. -bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { - if (lhs == nullptr) return rhs == nullptr; - if (rhs == nullptr) return false; - return posix::StrCaseCmp(lhs, rhs) == 0; -} - - // Compares two wide C strings, ignoring case. Returns true iff they - // have the same content. - // - // Unlike wcscasecmp(), this function can handle NULL argument(s). - // A NULL C string is considered different to any non-NULL wide C string, - // including the empty string. - // NB: The implementations on different platforms slightly differ. - // On windows, this method uses _wcsicmp which compares according to LC_CTYPE - // environment variable. On GNU platform this method uses wcscasecmp - // which compares according to LC_CTYPE category of the current locale. - // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the - // current locale. -bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, - const wchar_t* rhs) { - if (lhs == nullptr) return rhs == nullptr; - - if (rhs == nullptr) return false; - -#if GTEST_OS_WINDOWS - return _wcsicmp(lhs, rhs) == 0; -#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID - return wcscasecmp(lhs, rhs) == 0; -#else - // Android, Mac OS X and Cygwin don't define wcscasecmp. - // Other unknown OSes may not define it either. - wint_t left, right; - do { - left = towlower(*lhs++); - right = towlower(*rhs++); - } while (left && left == right); - return left == right; -#endif // OS selector -} - -// Returns true iff str ends with the given suffix, ignoring case. -// Any string is considered to end with an empty suffix. -bool String::EndsWithCaseInsensitive( - const std::string& str, const std::string& suffix) { - const size_t str_len = str.length(); - const size_t suffix_len = suffix.length(); - return (str_len >= suffix_len) && - CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len, - suffix.c_str()); -} - -// Formats an int value as "%02d". -std::string String::FormatIntWidth2(int value) { - std::stringstream ss; - ss << std::setfill('0') << std::setw(2) << value; - return ss.str(); -} - -// Formats an int value as "%X". -std::string String::FormatHexUInt32(UInt32 value) { - std::stringstream ss; - ss << std::hex << std::uppercase << value; - return ss.str(); -} - -// Formats an int value as "%X". -std::string String::FormatHexInt(int value) { - return FormatHexUInt32(static_cast(value)); -} - -// Formats a byte as "%02X". -std::string String::FormatByte(unsigned char value) { - std::stringstream ss; - ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase - << static_cast(value); - return ss.str(); -} - -// Converts the buffer in a stringstream to an std::string, converting NUL -// bytes to "\\0" along the way. -std::string StringStreamToString(::std::stringstream* ss) { - const ::std::string& str = ss->str(); - const char* const start = str.c_str(); - const char* const end = start + str.length(); - - std::string result; - result.reserve(static_cast(2 * (end - start))); - for (const char* ch = start; ch != end; ++ch) { - if (*ch == '\0') { - result += "\\0"; // Replaces NUL with "\\0"; - } else { - result += *ch; - } - } - - return result; -} - -// Appends the user-supplied message to the Google-Test-generated message. -std::string AppendUserMessage(const std::string& gtest_msg, - const Message& user_msg) { - // Appends the user message if it's non-empty. - const std::string user_msg_string = user_msg.GetString(); - if (user_msg_string.empty()) { - return gtest_msg; - } - - return gtest_msg + "\n" + user_msg_string; -} - -} // namespace internal - -// class TestResult - -// Creates an empty TestResult. -TestResult::TestResult() - : death_test_count_(0), - elapsed_time_(0) { -} - -// D'tor. -TestResult::~TestResult() { -} - -// Returns the i-th test part result among all the results. i can -// range from 0 to total_part_count() - 1. If i is not in that range, -// aborts the program. -const TestPartResult& TestResult::GetTestPartResult(int i) const { - if (i < 0 || i >= total_part_count()) - internal::posix::Abort(); - return test_part_results_.at(static_cast(i)); -} - -// Returns the i-th test property. i can range from 0 to -// test_property_count() - 1. If i is not in that range, aborts the -// program. -const TestProperty& TestResult::GetTestProperty(int i) const { - if (i < 0 || i >= test_property_count()) - internal::posix::Abort(); - return test_properties_.at(static_cast(i)); -} - -// Clears the test part results. -void TestResult::ClearTestPartResults() { - test_part_results_.clear(); -} - -// Adds a test part result to the list. -void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { - test_part_results_.push_back(test_part_result); -} - -// Adds a test property to the list. If a property with the same key as the -// supplied property is already represented, the value of this test_property -// replaces the old value for that key. -void TestResult::RecordProperty(const std::string& xml_element, - const TestProperty& test_property) { - if (!ValidateTestProperty(xml_element, test_property)) { - return; - } - internal::MutexLock lock(&test_properites_mutex_); - const std::vector::iterator property_with_matching_key = - std::find_if(test_properties_.begin(), test_properties_.end(), - internal::TestPropertyKeyIs(test_property.key())); - if (property_with_matching_key == test_properties_.end()) { - test_properties_.push_back(test_property); - return; - } - property_with_matching_key->SetValue(test_property.value()); -} - -// The list of reserved attributes used in the element of XML -// output. -static const char* const kReservedTestSuitesAttributes[] = { - "disabled", - "errors", - "failures", - "name", - "random_seed", - "tests", - "time", - "timestamp" -}; - -// The list of reserved attributes used in the element of XML -// output. -static const char* const kReservedTestSuiteAttributes[] = { - "disabled", - "errors", - "failures", - "name", - "tests", - "time" -}; - -// The list of reserved attributes used in the element of XML output. -static const char* const kReservedTestCaseAttributes[] = { - "classname", "name", "status", "time", "type_param", - "value_param", "file", "line"}; - -// Use a slightly different set for allowed output to ensure existing tests can -// still RecordProperty("result") -static const char* const kReservedOutputTestCaseAttributes[] = { - "classname", "name", "status", "time", "type_param", - "value_param", "file", "line", "result"}; - -template -std::vector ArrayAsVector(const char* const (&array)[kSize]) { - return std::vector(array, array + kSize); -} - -static std::vector GetReservedAttributesForElement( - const std::string& xml_element) { - if (xml_element == "testsuites") { - return ArrayAsVector(kReservedTestSuitesAttributes); - } else if (xml_element == "testsuite") { - return ArrayAsVector(kReservedTestSuiteAttributes); - } else if (xml_element == "testcase") { - return ArrayAsVector(kReservedTestCaseAttributes); - } else { - GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; - } - // This code is unreachable but some compilers may not realizes that. - return std::vector(); -} - -// TODO(jdesprez): Merge the two getReserved attributes once skip is improved -static std::vector GetReservedOutputAttributesForElement( - const std::string& xml_element) { - if (xml_element == "testsuites") { - return ArrayAsVector(kReservedTestSuitesAttributes); - } else if (xml_element == "testsuite") { - return ArrayAsVector(kReservedTestSuiteAttributes); - } else if (xml_element == "testcase") { - return ArrayAsVector(kReservedOutputTestCaseAttributes); - } else { - GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; - } - // This code is unreachable but some compilers may not realizes that. - return std::vector(); -} - -static std::string FormatWordList(const std::vector& words) { - Message word_list; - for (size_t i = 0; i < words.size(); ++i) { - if (i > 0 && words.size() > 2) { - word_list << ", "; - } - if (i == words.size() - 1) { - word_list << "and "; - } - word_list << "'" << words[i] << "'"; - } - return word_list.GetString(); -} - -static bool ValidateTestPropertyName( - const std::string& property_name, - const std::vector& reserved_names) { - if (std::find(reserved_names.begin(), reserved_names.end(), property_name) != - reserved_names.end()) { - ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name - << " (" << FormatWordList(reserved_names) - << " are reserved by " << GTEST_NAME_ << ")"; - return false; - } - return true; -} - -// Adds a failure if the key is a reserved attribute of the element named -// xml_element. Returns true if the property is valid. -bool TestResult::ValidateTestProperty(const std::string& xml_element, - const TestProperty& test_property) { - return ValidateTestPropertyName(test_property.key(), - GetReservedAttributesForElement(xml_element)); -} - -// Clears the object. -void TestResult::Clear() { - test_part_results_.clear(); - test_properties_.clear(); - death_test_count_ = 0; - elapsed_time_ = 0; -} - -// Returns true off the test part was skipped. -static bool TestPartSkipped(const TestPartResult& result) { - return result.skipped(); -} - -// Returns true iff the test was skipped. -bool TestResult::Skipped() const { - return !Failed() && CountIf(test_part_results_, TestPartSkipped) > 0; -} - -// Returns true iff the test failed. -bool TestResult::Failed() const { - for (int i = 0; i < total_part_count(); ++i) { - if (GetTestPartResult(i).failed()) - return true; - } - return false; -} - -// Returns true iff the test part fatally failed. -static bool TestPartFatallyFailed(const TestPartResult& result) { - return result.fatally_failed(); -} - -// Returns true iff the test fatally failed. -bool TestResult::HasFatalFailure() const { - return CountIf(test_part_results_, TestPartFatallyFailed) > 0; -} - -// Returns true iff the test part non-fatally failed. -static bool TestPartNonfatallyFailed(const TestPartResult& result) { - return result.nonfatally_failed(); -} - -// Returns true iff the test has a non-fatal failure. -bool TestResult::HasNonfatalFailure() const { - return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; -} - -// Gets the number of all test parts. This is the sum of the number -// of successful test parts and the number of failed test parts. -int TestResult::total_part_count() const { - return static_cast(test_part_results_.size()); -} - -// Returns the number of the test properties. -int TestResult::test_property_count() const { - return static_cast(test_properties_.size()); -} - -// class Test - -// Creates a Test object. - -// The c'tor saves the states of all flags. -Test::Test() - : gtest_flag_saver_(new GTEST_FLAG_SAVER_) { -} - -// The d'tor restores the states of all flags. The actual work is -// done by the d'tor of the gtest_flag_saver_ field, and thus not -// visible here. -Test::~Test() { -} - -// Sets up the test fixture. -// -// A sub-class may override this. -void Test::SetUp() { -} - -// Tears down the test fixture. -// -// A sub-class may override this. -void Test::TearDown() { -} - -// Allows user supplied key value pairs to be recorded for later output. -void Test::RecordProperty(const std::string& key, const std::string& value) { - UnitTest::GetInstance()->RecordProperty(key, value); -} - -// Allows user supplied key value pairs to be recorded for later output. -void Test::RecordProperty(const std::string& key, int value) { - Message value_message; - value_message << value; - RecordProperty(key, value_message.GetString().c_str()); -} - -namespace internal { - -void ReportFailureInUnknownLocation(TestPartResult::Type result_type, - const std::string& message) { - // This function is a friend of UnitTest and as such has access to - // AddTestPartResult. - UnitTest::GetInstance()->AddTestPartResult( - result_type, - nullptr, // No info about the source file where the exception occurred. - -1, // We have no info on which line caused the exception. - message, - ""); // No stack trace, either. -} - -} // namespace internal - -// Google Test requires all tests in the same test suite to use the same test -// fixture class. This function checks if the current test has the -// same fixture class as the first test in the current test suite. If -// yes, it returns true; otherwise it generates a Google Test failure and -// returns false. -bool Test::HasSameFixtureClass() { - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - const TestSuite* const test_suite = impl->current_test_suite(); - - // Info about the first test in the current test suite. - const TestInfo* const first_test_info = test_suite->test_info_list()[0]; - const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; - const char* const first_test_name = first_test_info->name(); - - // Info about the current test. - const TestInfo* const this_test_info = impl->current_test_info(); - const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; - const char* const this_test_name = this_test_info->name(); - - if (this_fixture_id != first_fixture_id) { - // Is the first test defined using TEST? - const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); - // Is this test defined using TEST? - const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); - - if (first_is_TEST || this_is_TEST) { - // Both TEST and TEST_F appear in same test suite, which is incorrect. - // Tell the user how to fix this. - - // Gets the name of the TEST and the name of the TEST_F. Note - // that first_is_TEST and this_is_TEST cannot both be true, as - // the fixture IDs are different for the two tests. - const char* const TEST_name = - first_is_TEST ? first_test_name : this_test_name; - const char* const TEST_F_name = - first_is_TEST ? this_test_name : first_test_name; - - ADD_FAILURE() - << "All tests in the same test suite must use the same test fixture\n" - << "class, so mixing TEST_F and TEST in the same test suite is\n" - << "illegal. In test suite " << this_test_info->test_suite_name() - << ",\n" - << "test " << TEST_F_name << " is defined using TEST_F but\n" - << "test " << TEST_name << " is defined using TEST. You probably\n" - << "want to change the TEST to TEST_F or move it to another test\n" - << "case."; - } else { - // Two fixture classes with the same name appear in two different - // namespaces, which is not allowed. Tell the user how to fix this. - ADD_FAILURE() - << "All tests in the same test suite must use the same test fixture\n" - << "class. However, in test suite " - << this_test_info->test_suite_name() << ",\n" - << "you defined test " << first_test_name << " and test " - << this_test_name << "\n" - << "using two different test fixture classes. This can happen if\n" - << "the two classes are from different namespaces or translation\n" - << "units and have the same name. You should probably rename one\n" - << "of the classes to put the tests into different test suites."; - } - return false; - } - - return true; -} - -#if GTEST_HAS_SEH - -// Adds an "exception thrown" fatal failure to the current test. This -// function returns its result via an output parameter pointer because VC++ -// prohibits creation of objects with destructors on stack in functions -// using __try (see error C2712). -static std::string* FormatSehExceptionMessage(DWORD exception_code, - const char* location) { - Message message; - message << "SEH exception with code 0x" << std::setbase(16) << - exception_code << std::setbase(10) << " thrown in " << location << "."; - - return new std::string(message.GetString()); -} - -#endif // GTEST_HAS_SEH - -namespace internal { - -#if GTEST_HAS_EXCEPTIONS - -// Adds an "exception thrown" fatal failure to the current test. -static std::string FormatCxxExceptionMessage(const char* description, - const char* location) { - Message message; - if (description != nullptr) { - message << "C++ exception with description \"" << description << "\""; - } else { - message << "Unknown C++ exception"; - } - message << " thrown in " << location << "."; - - return message.GetString(); -} - -static std::string PrintTestPartResultToString( - const TestPartResult& test_part_result); - -GoogleTestFailureException::GoogleTestFailureException( - const TestPartResult& failure) - : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} - -#endif // GTEST_HAS_EXCEPTIONS - -// We put these helper functions in the internal namespace as IBM's xlC -// compiler rejects the code if they were declared static. - -// Runs the given method and handles SEH exceptions it throws, when -// SEH is supported; returns the 0-value for type Result in case of an -// SEH exception. (Microsoft compilers cannot handle SEH and C++ -// exceptions in the same function. Therefore, we provide a separate -// wrapper function for handling SEH exceptions.) -template -Result HandleSehExceptionsInMethodIfSupported( - T* object, Result (T::*method)(), const char* location) { -#if GTEST_HAS_SEH - __try { - return (object->*method)(); - } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT - GetExceptionCode())) { - // We create the exception message on the heap because VC++ prohibits - // creation of objects with destructors on stack in functions using __try - // (see error C2712). - std::string* exception_message = FormatSehExceptionMessage( - GetExceptionCode(), location); - internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, - *exception_message); - delete exception_message; - return static_cast(0); - } -#else - (void)location; - return (object->*method)(); -#endif // GTEST_HAS_SEH -} - -// Runs the given method and catches and reports C++ and/or SEH-style -// exceptions, if they are supported; returns the 0-value for type -// Result in case of an SEH exception. -template -Result HandleExceptionsInMethodIfSupported( - T* object, Result (T::*method)(), const char* location) { - // NOTE: The user code can affect the way in which Google Test handles - // exceptions by setting GTEST_FLAG(catch_exceptions), but only before - // RUN_ALL_TESTS() starts. It is technically possible to check the flag - // after the exception is caught and either report or re-throw the - // exception based on the flag's value: - // - // try { - // // Perform the test method. - // } catch (...) { - // if (GTEST_FLAG(catch_exceptions)) - // // Report the exception as failure. - // else - // throw; // Re-throws the original exception. - // } - // - // However, the purpose of this flag is to allow the program to drop into - // the debugger when the exception is thrown. On most platforms, once the - // control enters the catch block, the exception origin information is - // lost and the debugger will stop the program at the point of the - // re-throw in this function -- instead of at the point of the original - // throw statement in the code under test. For this reason, we perform - // the check early, sacrificing the ability to affect Google Test's - // exception handling in the method where the exception is thrown. - if (internal::GetUnitTestImpl()->catch_exceptions()) { -#if GTEST_HAS_EXCEPTIONS - try { - return HandleSehExceptionsInMethodIfSupported(object, method, location); - } catch (const AssertionException&) { // NOLINT - // This failure was reported already. - } catch (const internal::GoogleTestFailureException&) { // NOLINT - // This exception type can only be thrown by a failed Google - // Test assertion with the intention of letting another testing - // framework catch it. Therefore we just re-throw it. - throw; - } catch (const std::exception& e) { // NOLINT - internal::ReportFailureInUnknownLocation( - TestPartResult::kFatalFailure, - FormatCxxExceptionMessage(e.what(), location)); - } catch (...) { // NOLINT - internal::ReportFailureInUnknownLocation( - TestPartResult::kFatalFailure, - FormatCxxExceptionMessage(nullptr, location)); - } - return static_cast(0); -#else - return HandleSehExceptionsInMethodIfSupported(object, method, location); -#endif // GTEST_HAS_EXCEPTIONS - } else { - return (object->*method)(); - } -} - -} // namespace internal - -// Runs the test and updates the test result. -void Test::Run() { - if (!HasSameFixtureClass()) return; - - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); - // We will run the test only if SetUp() was successful and didn't call - // GTEST_SKIP(). - if (!HasFatalFailure() && !IsSkipped()) { - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported( - this, &Test::TestBody, "the test body"); - } - - // However, we want to clean up as much as possible. Hence we will - // always call TearDown(), even if SetUp() or the test body has - // failed. - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported( - this, &Test::TearDown, "TearDown()"); -} - -// Returns true iff the current test has a fatal failure. -bool Test::HasFatalFailure() { - return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); -} - -// Returns true iff the current test has a non-fatal failure. -bool Test::HasNonfatalFailure() { - return internal::GetUnitTestImpl()->current_test_result()-> - HasNonfatalFailure(); -} - -// Returns true iff the current test was skipped. -bool Test::IsSkipped() { - return internal::GetUnitTestImpl()->current_test_result()->Skipped(); -} - -// class TestInfo - -// Constructs a TestInfo object. It assumes ownership of the test factory -// object. -TestInfo::TestInfo(const std::string& a_test_suite_name, - const std::string& a_name, const char* a_type_param, - const char* a_value_param, - internal::CodeLocation a_code_location, - internal::TypeId fixture_class_id, - internal::TestFactoryBase* factory) - : test_suite_name_(a_test_suite_name), - name_(a_name), - type_param_(a_type_param ? new std::string(a_type_param) : nullptr), - value_param_(a_value_param ? new std::string(a_value_param) : nullptr), - location_(a_code_location), - fixture_class_id_(fixture_class_id), - should_run_(false), - is_disabled_(false), - matches_filter_(false), - factory_(factory), - result_() {} - -// Destructs a TestInfo object. -TestInfo::~TestInfo() { delete factory_; } - -namespace internal { - -// Creates a new TestInfo object and registers it with Google Test; -// returns the created object. -// -// Arguments: -// -// test_suite_name: name of the test suite -// name: name of the test -// type_param: the name of the test's type parameter, or NULL if -// this is not a typed or a type-parameterized test. -// value_param: text representation of the test's value parameter, -// or NULL if this is not a value-parameterized test. -// code_location: code location where the test is defined -// fixture_class_id: ID of the test fixture class -// set_up_tc: pointer to the function that sets up the test suite -// tear_down_tc: pointer to the function that tears down the test suite -// factory: pointer to the factory that creates a test object. -// The newly created TestInfo instance will assume -// ownership of the factory object. -TestInfo* MakeAndRegisterTestInfo( - const char* test_suite_name, const char* name, const char* type_param, - const char* value_param, CodeLocation code_location, - TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, - TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) { - TestInfo* const test_info = - new TestInfo(test_suite_name, name, type_param, value_param, - code_location, fixture_class_id, factory); - GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); - return test_info; -} - -void ReportInvalidTestSuiteType(const char* test_suite_name, - CodeLocation code_location) { - Message errors; - errors - << "Attempted redefinition of test suite " << test_suite_name << ".\n" - << "All tests in the same test suite must use the same test fixture\n" - << "class. However, in test suite " << test_suite_name << ", you tried\n" - << "to define a test using a fixture class different from the one\n" - << "used earlier. This can happen if the two fixture classes are\n" - << "from different namespaces and have the same name. You should\n" - << "probably rename one of the classes to put the tests into different\n" - << "test suites."; - - GTEST_LOG_(ERROR) << FormatFileLocation(code_location.file.c_str(), - code_location.line) - << " " << errors.GetString(); -} -} // namespace internal - -namespace { - -// A predicate that checks the test name of a TestInfo against a known -// value. -// -// This is used for implementation of the TestSuite class only. We put -// it in the anonymous namespace to prevent polluting the outer -// namespace. -// -// TestNameIs is copyable. -class TestNameIs { - public: - // Constructor. - // - // TestNameIs has NO default constructor. - explicit TestNameIs(const char* name) - : name_(name) {} - - // Returns true iff the test name of test_info matches name_. - bool operator()(const TestInfo * test_info) const { - return test_info && test_info->name() == name_; - } - - private: - std::string name_; -}; - -} // namespace - -namespace internal { - -// This method expands all parameterized tests registered with macros TEST_P -// and INSTANTIATE_TEST_SUITE_P into regular tests and registers those. -// This will be done just once during the program runtime. -void UnitTestImpl::RegisterParameterizedTests() { - if (!parameterized_tests_registered_) { - parameterized_test_registry_.RegisterTests(); - parameterized_tests_registered_ = true; - } -} - -} // namespace internal - -// Creates the test object, runs it, records its result, and then -// deletes it. -void TestInfo::Run() { - if (!should_run_) return; - - // Tells UnitTest where to store test result. - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - impl->set_current_test_info(this); - - TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); - - // Notifies the unit test event listeners that a test is about to start. - repeater->OnTestStart(*this); - - const TimeInMillis start = internal::GetTimeInMillis(); - - impl->os_stack_trace_getter()->UponLeavingGTest(); - - // Creates the test object. - Test* const test = internal::HandleExceptionsInMethodIfSupported( - factory_, &internal::TestFactoryBase::CreateTest, - "the test fixture's constructor"); - - // Runs the test if the constructor didn't generate a fatal failure or invoke - // GTEST_SKIP(). - // Note that the object will not be null - if (!Test::HasFatalFailure() && !Test::IsSkipped()) { - // This doesn't throw as all user code that can throw are wrapped into - // exception handling code. - test->Run(); - } - - if (test != nullptr) { - // Deletes the test object. - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported( - test, &Test::DeleteSelf_, "the test fixture's destructor"); - } - - result_.set_elapsed_time(internal::GetTimeInMillis() - start); - - // Notifies the unit test event listener that a test has just finished. - repeater->OnTestEnd(*this); - - // Tells UnitTest to stop associating assertion results to this - // test. - impl->set_current_test_info(nullptr); -} - -// class TestSuite - -// Gets the number of successful tests in this test suite. -int TestSuite::successful_test_count() const { - return CountIf(test_info_list_, TestPassed); -} - -// Gets the number of successful tests in this test suite. -int TestSuite::skipped_test_count() const { - return CountIf(test_info_list_, TestSkipped); -} - -// Gets the number of failed tests in this test suite. -int TestSuite::failed_test_count() const { - return CountIf(test_info_list_, TestFailed); -} - -// Gets the number of disabled tests that will be reported in the XML report. -int TestSuite::reportable_disabled_test_count() const { - return CountIf(test_info_list_, TestReportableDisabled); -} - -// Gets the number of disabled tests in this test suite. -int TestSuite::disabled_test_count() const { - return CountIf(test_info_list_, TestDisabled); -} - -// Gets the number of tests to be printed in the XML report. -int TestSuite::reportable_test_count() const { - return CountIf(test_info_list_, TestReportable); -} - -// Get the number of tests in this test suite that should run. -int TestSuite::test_to_run_count() const { - return CountIf(test_info_list_, ShouldRunTest); -} - -// Gets the number of all tests. -int TestSuite::total_test_count() const { - return static_cast(test_info_list_.size()); -} - -// Creates a TestSuite with the given name. -// -// Arguments: -// -// name: name of the test suite -// a_type_param: the name of the test suite's type parameter, or NULL if -// this is not a typed or a type-parameterized test suite. -// set_up_tc: pointer to the function that sets up the test suite -// tear_down_tc: pointer to the function that tears down the test suite -TestSuite::TestSuite(const char* a_name, const char* a_type_param, - internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc) - : name_(a_name), - type_param_(a_type_param ? new std::string(a_type_param) : nullptr), - set_up_tc_(set_up_tc), - tear_down_tc_(tear_down_tc), - should_run_(false), - elapsed_time_(0) {} - -// Destructor of TestSuite. -TestSuite::~TestSuite() { - // Deletes every Test in the collection. - ForEach(test_info_list_, internal::Delete); -} - -// Returns the i-th test among all the tests. i can range from 0 to -// total_test_count() - 1. If i is not in that range, returns NULL. -const TestInfo* TestSuite::GetTestInfo(int i) const { - const int index = GetElementOr(test_indices_, i, -1); - return index < 0 ? nullptr : test_info_list_[static_cast(index)]; -} - -// Returns the i-th test among all the tests. i can range from 0 to -// total_test_count() - 1. If i is not in that range, returns NULL. -TestInfo* TestSuite::GetMutableTestInfo(int i) { - const int index = GetElementOr(test_indices_, i, -1); - return index < 0 ? nullptr : test_info_list_[static_cast(index)]; -} - -// Adds a test to this test suite. Will delete the test upon -// destruction of the TestSuite object. -void TestSuite::AddTestInfo(TestInfo* test_info) { - test_info_list_.push_back(test_info); - test_indices_.push_back(static_cast(test_indices_.size())); -} - -// Runs every test in this TestSuite. -void TestSuite::Run() { - if (!should_run_) return; - - internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); - impl->set_current_test_suite(this); - - TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); - - // Call both legacy and the new API - repeater->OnTestSuiteStart(*this); -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI - repeater->OnTestCaseStart(*this); -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI - - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported( - this, &TestSuite::RunSetUpTestSuite, "SetUpTestSuite()"); - - const internal::TimeInMillis start = internal::GetTimeInMillis(); - for (int i = 0; i < total_test_count(); i++) { - GetMutableTestInfo(i)->Run(); - } - elapsed_time_ = internal::GetTimeInMillis() - start; - - impl->os_stack_trace_getter()->UponLeavingGTest(); - internal::HandleExceptionsInMethodIfSupported( - this, &TestSuite::RunTearDownTestSuite, "TearDownTestSuite()"); - - // Call both legacy and the new API - repeater->OnTestSuiteEnd(*this); -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI - repeater->OnTestCaseEnd(*this); -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI - - impl->set_current_test_suite(nullptr); -} - -// Clears the results of all tests in this test suite. -void TestSuite::ClearResult() { - ad_hoc_test_result_.Clear(); - ForEach(test_info_list_, TestInfo::ClearTestResult); -} - -// Shuffles the tests in this test suite. -void TestSuite::ShuffleTests(internal::Random* random) { - Shuffle(random, &test_indices_); -} - -// Restores the test order to before the first shuffle. -void TestSuite::UnshuffleTests() { - for (size_t i = 0; i < test_indices_.size(); i++) { - test_indices_[i] = static_cast(i); - } -} - -// Formats a countable noun. Depending on its quantity, either the -// singular form or the plural form is used. e.g. -// -// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". -// FormatCountableNoun(5, "book", "books") returns "5 books". -static std::string FormatCountableNoun(int count, - const char * singular_form, - const char * plural_form) { - return internal::StreamableToString(count) + " " + - (count == 1 ? singular_form : plural_form); -} - -// Formats the count of tests. -static std::string FormatTestCount(int test_count) { - return FormatCountableNoun(test_count, "test", "tests"); -} - -// Formats the count of test suites. -static std::string FormatTestSuiteCount(int test_suite_count) { - return FormatCountableNoun(test_suite_count, "test suite", "test suites"); -} - -// Converts a TestPartResult::Type enum to human-friendly string -// representation. Both kNonFatalFailure and kFatalFailure are translated -// to "Failure", as the user usually doesn't care about the difference -// between the two when viewing the test result. -static const char * TestPartResultTypeToString(TestPartResult::Type type) { - switch (type) { - case TestPartResult::kSkip: - return "Skipped"; - case TestPartResult::kSuccess: - return "Success"; - - case TestPartResult::kNonFatalFailure: - case TestPartResult::kFatalFailure: -#ifdef _MSC_VER - return "error: "; -#else - return "Failure\n"; -#endif - default: - return "Unknown result type"; - } -} - -namespace internal { - -// Prints a TestPartResult to an std::string. -static std::string PrintTestPartResultToString( - const TestPartResult& test_part_result) { - return (Message() - << internal::FormatFileLocation(test_part_result.file_name(), - test_part_result.line_number()) - << " " << TestPartResultTypeToString(test_part_result.type()) - << test_part_result.message()).GetString(); -} - -// Prints a TestPartResult. -static void PrintTestPartResult(const TestPartResult& test_part_result) { - const std::string& result = - PrintTestPartResultToString(test_part_result); - printf("%s\n", result.c_str()); - fflush(stdout); - // If the test program runs in Visual Studio or a debugger, the - // following statements add the test part result message to the Output - // window such that the user can double-click on it to jump to the - // corresponding source code location; otherwise they do nothing. -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE - // We don't call OutputDebugString*() on Windows Mobile, as printing - // to stdout is done by OutputDebugString() there already - we don't - // want the same message printed twice. - ::OutputDebugStringA(result.c_str()); - ::OutputDebugStringA("\n"); -#endif -} - -// class PrettyUnitTestResultPrinter -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ - !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW - -// Returns the character attribute for the given color. -static WORD GetColorAttribute(GTestColor color) { - switch (color) { - case COLOR_RED: return FOREGROUND_RED; - case COLOR_GREEN: return FOREGROUND_GREEN; - case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN; - default: return 0; - } -} - -static int GetBitOffset(WORD color_mask) { - if (color_mask == 0) return 0; - - int bitOffset = 0; - while ((color_mask & 1) == 0) { - color_mask >>= 1; - ++bitOffset; - } - return bitOffset; -} - -static WORD GetNewColor(GTestColor color, WORD old_color_attrs) { - // Let's reuse the BG - static const WORD background_mask = BACKGROUND_BLUE | BACKGROUND_GREEN | - BACKGROUND_RED | BACKGROUND_INTENSITY; - static const WORD foreground_mask = FOREGROUND_BLUE | FOREGROUND_GREEN | - FOREGROUND_RED | FOREGROUND_INTENSITY; - const WORD existing_bg = old_color_attrs & background_mask; - - WORD new_color = - GetColorAttribute(color) | existing_bg | FOREGROUND_INTENSITY; - static const int bg_bitOffset = GetBitOffset(background_mask); - static const int fg_bitOffset = GetBitOffset(foreground_mask); - - if (((new_color & background_mask) >> bg_bitOffset) == - ((new_color & foreground_mask) >> fg_bitOffset)) { - new_color ^= FOREGROUND_INTENSITY; // invert intensity - } - return new_color; -} - -#else - -// Returns the ANSI color code for the given color. COLOR_DEFAULT is -// an invalid input. -static const char* GetAnsiColorCode(GTestColor color) { - switch (color) { - case COLOR_RED: return "1"; - case COLOR_GREEN: return "2"; - case COLOR_YELLOW: return "3"; - default: - return nullptr; - } -} - -#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE - -// Returns true iff Google Test should use colors in the output. -bool ShouldUseColor(bool stdout_is_tty) { - const char* const gtest_color = GTEST_FLAG(color).c_str(); - - if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW - // On Windows the TERM variable is usually not set, but the - // console there does support colors. - return stdout_is_tty; -#else - // On non-Windows platforms, we rely on the TERM variable. - const char* const term = posix::GetEnv("TERM"); - const bool term_supports_color = - String::CStringEquals(term, "xterm") || - String::CStringEquals(term, "xterm-color") || - String::CStringEquals(term, "xterm-256color") || - String::CStringEquals(term, "screen") || - String::CStringEquals(term, "screen-256color") || - String::CStringEquals(term, "tmux") || - String::CStringEquals(term, "tmux-256color") || - String::CStringEquals(term, "rxvt-unicode") || - String::CStringEquals(term, "rxvt-unicode-256color") || - String::CStringEquals(term, "linux") || - String::CStringEquals(term, "cygwin"); - return stdout_is_tty && term_supports_color; -#endif // GTEST_OS_WINDOWS - } - - return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || - String::CaseInsensitiveCStringEquals(gtest_color, "true") || - String::CaseInsensitiveCStringEquals(gtest_color, "t") || - String::CStringEquals(gtest_color, "1"); - // We take "yes", "true", "t", and "1" as meaning "yes". If the - // value is neither one of these nor "auto", we treat it as "no" to - // be conservative. -} - -// Helpers for printing colored strings to stdout. Note that on Windows, we -// cannot simply emit special characters and have the terminal change colors. -// This routine must actually emit the characters rather than return a string -// that would be colored when printed, as can be done on Linux. -void ColoredPrintf(GTestColor color, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - -#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS || GTEST_OS_IOS || \ - GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT - const bool use_color = AlwaysFalse(); -#else - static const bool in_color_mode = - ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); - const bool use_color = in_color_mode && (color != COLOR_DEFAULT); -#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS - - if (!use_color) { - vprintf(fmt, args); - va_end(args); - return; - } - -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \ - !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW - const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); - - // Gets the current text color. - CONSOLE_SCREEN_BUFFER_INFO buffer_info; - GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); - const WORD old_color_attrs = buffer_info.wAttributes; - const WORD new_color = GetNewColor(color, old_color_attrs); - - // We need to flush the stream buffers into the console before each - // SetConsoleTextAttribute call lest it affect the text that is already - // printed but has not yet reached the console. - fflush(stdout); - SetConsoleTextAttribute(stdout_handle, new_color); - - vprintf(fmt, args); - - fflush(stdout); - // Restores the text color. - SetConsoleTextAttribute(stdout_handle, old_color_attrs); -#else - printf("\033[0;3%sm", GetAnsiColorCode(color)); - vprintf(fmt, args); - printf("\033[m"); // Resets the terminal to default. -#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE - va_end(args); -} - -// Text printed in Google Test's text output and --gtest_list_tests -// output to label the type parameter and value parameter for a test. -static const char kTypeParamLabel[] = "TypeParam"; -static const char kValueParamLabel[] = "GetParam()"; - -static void PrintFullTestCommentIfPresent(const TestInfo& test_info) { - const char* const type_param = test_info.type_param(); - const char* const value_param = test_info.value_param(); - - if (type_param != nullptr || value_param != nullptr) { - printf(", where "); - if (type_param != nullptr) { - printf("%s = %s", kTypeParamLabel, type_param); - if (value_param != nullptr) printf(" and "); - } - if (value_param != nullptr) { - printf("%s = %s", kValueParamLabel, value_param); - } - } -} - -// This class implements the TestEventListener interface. -// -// Class PrettyUnitTestResultPrinter is copyable. -class PrettyUnitTestResultPrinter : public TestEventListener { - public: - PrettyUnitTestResultPrinter() {} - static void PrintTestName(const char* test_suite, const char* test) { - printf("%s.%s", test_suite, test); - } - - // The following methods override what's in the TestEventListener class. - void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} - void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; - void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override; - void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} - void OnTestCaseStart(const TestSuite& test_suite) override; - void OnTestStart(const TestInfo& test_info) override; - void OnTestPartResult(const TestPartResult& result) override; - void OnTestEnd(const TestInfo& test_info) override; - void OnTestCaseEnd(const TestSuite& test_suite) override; - void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override; - void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; - void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} - - private: - static void PrintFailedTests(const UnitTest& unit_test); - static void PrintSkippedTests(const UnitTest& unit_test); -}; - - // Fired before each iteration of tests starts. -void PrettyUnitTestResultPrinter::OnTestIterationStart( - const UnitTest& unit_test, int iteration) { - if (GTEST_FLAG(repeat) != 1) - printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); - - const char* const filter = GTEST_FLAG(filter).c_str(); - - // Prints the filter if it's not *. This reminds the user that some - // tests may be skipped. - if (!String::CStringEquals(filter, kUniversalFilter)) { - ColoredPrintf(COLOR_YELLOW, - "Note: %s filter = %s\n", GTEST_NAME_, filter); - } - - if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { - const Int32 shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); - ColoredPrintf(COLOR_YELLOW, - "Note: This is test shard %d of %s.\n", - static_cast(shard_index) + 1, - internal::posix::GetEnv(kTestTotalShards)); - } - - if (GTEST_FLAG(shuffle)) { - ColoredPrintf(COLOR_YELLOW, - "Note: Randomizing tests' orders with a seed of %d .\n", - unit_test.random_seed()); - } - - ColoredPrintf(COLOR_GREEN, "[==========] "); - printf("Running %s from %s.\n", - FormatTestCount(unit_test.test_to_run_count()).c_str(), - FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); - fflush(stdout); -} - -void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( - const UnitTest& /*unit_test*/) { - ColoredPrintf(COLOR_GREEN, "[----------] "); - printf("Global test environment set-up.\n"); - fflush(stdout); -} - -void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestSuite& test_suite) { - const std::string counts = - FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); - ColoredPrintf(COLOR_GREEN, "[----------] "); - printf("%s from %s", counts.c_str(), test_suite.name()); - if (test_suite.type_param() == nullptr) { - printf("\n"); - } else { - printf(", where %s = %s\n", kTypeParamLabel, test_suite.type_param()); - } - fflush(stdout); -} - -void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { - ColoredPrintf(COLOR_GREEN, "[ RUN ] "); - PrintTestName(test_info.test_suite_name(), test_info.name()); - printf("\n"); - fflush(stdout); -} - -// Called after an assertion failure. -void PrettyUnitTestResultPrinter::OnTestPartResult( - const TestPartResult& result) { - switch (result.type()) { - // If the test part succeeded, or was skipped, - // we don't need to do anything. - case TestPartResult::kSkip: - case TestPartResult::kSuccess: - return; - default: - // Print failure message from the assertion - // (e.g. expected this and got that). - PrintTestPartResult(result); - fflush(stdout); - } -} - -void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { - if (test_info.result()->Passed()) { - ColoredPrintf(COLOR_GREEN, "[ OK ] "); - } else if (test_info.result()->Skipped()) { - ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); - } else { - ColoredPrintf(COLOR_RED, "[ FAILED ] "); - } - PrintTestName(test_info.test_suite_name(), test_info.name()); - if (test_info.result()->Failed()) - PrintFullTestCommentIfPresent(test_info); - - if (GTEST_FLAG(print_time)) { - printf(" (%s ms)\n", internal::StreamableToString( - test_info.result()->elapsed_time()).c_str()); - } else { - printf("\n"); - } - fflush(stdout); -} - -void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestSuite& test_suite) { - if (!GTEST_FLAG(print_time)) return; - - const std::string counts = - FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests"); - ColoredPrintf(COLOR_GREEN, "[----------] "); - printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_suite.name(), - internal::StreamableToString(test_suite.elapsed_time()).c_str()); - fflush(stdout); -} - -void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( - const UnitTest& /*unit_test*/) { - ColoredPrintf(COLOR_GREEN, "[----------] "); - printf("Global test environment tear-down\n"); - fflush(stdout); -} - -// Internal helper for printing the list of failed tests. -void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { - const int failed_test_count = unit_test.failed_test_count(); - if (failed_test_count == 0) { - return; - } - - for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { - const TestSuite& test_suite = *unit_test.GetTestSuite(i); - if (!test_suite.should_run() || (test_suite.failed_test_count() == 0)) { - continue; - } - for (int j = 0; j < test_suite.total_test_count(); ++j) { - const TestInfo& test_info = *test_suite.GetTestInfo(j); - if (!test_info.should_run() || !test_info.result()->Failed()) { - continue; - } - ColoredPrintf(COLOR_RED, "[ FAILED ] "); - printf("%s.%s", test_suite.name(), test_info.name()); - PrintFullTestCommentIfPresent(test_info); - printf("\n"); - } - } -} - -// Internal helper for printing the list of skipped tests. -void PrettyUnitTestResultPrinter::PrintSkippedTests(const UnitTest& unit_test) { - const int skipped_test_count = unit_test.skipped_test_count(); - if (skipped_test_count == 0) { - return; - } - - for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { - const TestSuite& test_suite = *unit_test.GetTestSuite(i); - if (!test_suite.should_run() || (test_suite.skipped_test_count() == 0)) { - continue; - } - for (int j = 0; j < test_suite.total_test_count(); ++j) { - const TestInfo& test_info = *test_suite.GetTestInfo(j); - if (!test_info.should_run() || !test_info.result()->Skipped()) { - continue; - } - ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); - printf("%s.%s", test_suite.name(), test_info.name()); - printf("\n"); - } - } -} - -void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, - int /*iteration*/) { - ColoredPrintf(COLOR_GREEN, "[==========] "); - printf("%s from %s ran.", - FormatTestCount(unit_test.test_to_run_count()).c_str(), - FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str()); - if (GTEST_FLAG(print_time)) { - printf(" (%s ms total)", - internal::StreamableToString(unit_test.elapsed_time()).c_str()); - } - printf("\n"); - ColoredPrintf(COLOR_GREEN, "[ PASSED ] "); - printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); - - const int skipped_test_count = unit_test.skipped_test_count(); - if (skipped_test_count > 0) { - ColoredPrintf(COLOR_GREEN, "[ SKIPPED ] "); - printf("%s, listed below:\n", FormatTestCount(skipped_test_count).c_str()); - PrintSkippedTests(unit_test); - } - - int num_failures = unit_test.failed_test_count(); - if (!unit_test.Passed()) { - const int failed_test_count = unit_test.failed_test_count(); - ColoredPrintf(COLOR_RED, "[ FAILED ] "); - printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); - PrintFailedTests(unit_test); - printf("\n%2d FAILED %s\n", num_failures, - num_failures == 1 ? "TEST" : "TESTS"); - } - - int num_disabled = unit_test.reportable_disabled_test_count(); - if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { - if (!num_failures) { - printf("\n"); // Add a spacer if no FAILURE banner is displayed. - } - ColoredPrintf(COLOR_YELLOW, - " YOU HAVE %d DISABLED %s\n\n", - num_disabled, - num_disabled == 1 ? "TEST" : "TESTS"); - } - // Ensure that Google Test output is printed before, e.g., heapchecker output. - fflush(stdout); -} - -// End PrettyUnitTestResultPrinter - -// class TestEventRepeater -// -// This class forwards events to other event listeners. -class TestEventRepeater : public TestEventListener { - public: - TestEventRepeater() : forwarding_enabled_(true) {} - ~TestEventRepeater() override; - void Append(TestEventListener *listener); - TestEventListener* Release(TestEventListener* listener); - - // Controls whether events will be forwarded to listeners_. Set to false - // in death test child processes. - bool forwarding_enabled() const { return forwarding_enabled_; } - void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } - - void OnTestProgramStart(const UnitTest& unit_test) override; - void OnTestIterationStart(const UnitTest& unit_test, int iteration) override; - void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override; - void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override; -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI - void OnTestCaseStart(const TestSuite& parameter) override; -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI - void OnTestSuiteStart(const TestSuite& parameter) override; - void OnTestStart(const TestInfo& test_info) override; - void OnTestPartResult(const TestPartResult& result) override; - void OnTestEnd(const TestInfo& test_info) override; -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI - void OnTestCaseEnd(const TestSuite& parameter) override; -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI - void OnTestSuiteEnd(const TestSuite& parameter) override; - void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override; - void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override; - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; - void OnTestProgramEnd(const UnitTest& unit_test) override; - - private: - // Controls whether events will be forwarded to listeners_. Set to false - // in death test child processes. - bool forwarding_enabled_; - // The list of listeners that receive events. - std::vector listeners_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater); -}; - -TestEventRepeater::~TestEventRepeater() { - ForEach(listeners_, Delete); -} - -void TestEventRepeater::Append(TestEventListener *listener) { - listeners_.push_back(listener); -} - -TestEventListener* TestEventRepeater::Release(TestEventListener *listener) { - for (size_t i = 0; i < listeners_.size(); ++i) { - if (listeners_[i] == listener) { - listeners_.erase(listeners_.begin() + static_cast(i)); - return listener; - } - } - - return nullptr; -} - -// Since most methods are very similar, use macros to reduce boilerplate. -// This defines a member that forwards the call to all listeners. -#define GTEST_REPEATER_METHOD_(Name, Type) \ -void TestEventRepeater::Name(const Type& parameter) { \ - if (forwarding_enabled_) { \ - for (size_t i = 0; i < listeners_.size(); i++) { \ - listeners_[i]->Name(parameter); \ - } \ - } \ -} -// This defines a member that forwards the call to all listeners in reverse -// order. -#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ - void TestEventRepeater::Name(const Type& parameter) { \ - if (forwarding_enabled_) { \ - for (size_t i = listeners_.size(); i != 0; i--) { \ - listeners_[i - 1]->Name(parameter); \ - } \ - } \ - } - -GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) -GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -GTEST_REPEATER_METHOD_(OnTestCaseStart, TestSuite) -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -GTEST_REPEATER_METHOD_(OnTestSuiteStart, TestSuite) -GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) -GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) -GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) -GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) -GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) -GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestSuite) -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -GTEST_REVERSE_REPEATER_METHOD_(OnTestSuiteEnd, TestSuite) -GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) - -#undef GTEST_REPEATER_METHOD_ -#undef GTEST_REVERSE_REPEATER_METHOD_ - -void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, - int iteration) { - if (forwarding_enabled_) { - for (size_t i = 0; i < listeners_.size(); i++) { - listeners_[i]->OnTestIterationStart(unit_test, iteration); - } - } -} - -void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, - int iteration) { - if (forwarding_enabled_) { - for (size_t i = listeners_.size(); i > 0; i--) { - listeners_[i - 1]->OnTestIterationEnd(unit_test, iteration); - } - } -} - -// End TestEventRepeater - -// This class generates an XML output file. -class XmlUnitTestResultPrinter : public EmptyTestEventListener { - public: - explicit XmlUnitTestResultPrinter(const char* output_file); - - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; - void ListTestsMatchingFilter(const std::vector& test_suites); - - // Prints an XML summary of all unit tests. - static void PrintXmlTestsList(std::ostream* stream, - const std::vector& test_suites); - - private: - // Is c a whitespace character that is normalized to a space character - // when it appears in an XML attribute value? - static bool IsNormalizableWhitespace(char c) { - return c == 0x9 || c == 0xA || c == 0xD; - } - - // May c appear in a well-formed XML document? - static bool IsValidXmlCharacter(char c) { - return IsNormalizableWhitespace(c) || c >= 0x20; - } - - // Returns an XML-escaped copy of the input string str. If - // is_attribute is true, the text is meant to appear as an attribute - // value, and normalizable whitespace is preserved by replacing it - // with character references. - static std::string EscapeXml(const std::string& str, bool is_attribute); - - // Returns the given string with all characters invalid in XML removed. - static std::string RemoveInvalidXmlCharacters(const std::string& str); - - // Convenience wrapper around EscapeXml when str is an attribute value. - static std::string EscapeXmlAttribute(const std::string& str) { - return EscapeXml(str, true); - } - - // Convenience wrapper around EscapeXml when str is not an attribute value. - static std::string EscapeXmlText(const char* str) { - return EscapeXml(str, false); - } - - // Verifies that the given attribute belongs to the given element and - // streams the attribute as XML. - static void OutputXmlAttribute(std::ostream* stream, - const std::string& element_name, - const std::string& name, - const std::string& value); - - // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. - static void OutputXmlCDataSection(::std::ostream* stream, const char* data); - - // Streams an XML representation of a TestInfo object. - static void OutputXmlTestInfo(::std::ostream* stream, - const char* test_suite_name, - const TestInfo& test_info); - - // Prints an XML representation of a TestSuite object - static void PrintXmlTestSuite(::std::ostream* stream, - const TestSuite& test_suite); - - // Prints an XML summary of unit_test to output stream out. - static void PrintXmlUnitTest(::std::ostream* stream, - const UnitTest& unit_test); - - // Produces a string representing the test properties in a result as space - // delimited XML attributes based on the property key="value" pairs. - // When the std::string is not empty, it includes a space at the beginning, - // to delimit this attribute from prior attributes. - static std::string TestPropertiesAsXmlAttributes(const TestResult& result); - - // Streams an XML representation of the test properties of a TestResult - // object. - static void OutputXmlTestProperties(std::ostream* stream, - const TestResult& result); - - // The output file. - const std::string output_file_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter); -}; - -// Creates a new XmlUnitTestResultPrinter. -XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) - : output_file_(output_file) { - if (output_file_.empty()) { - GTEST_LOG_(FATAL) << "XML output file may not be null"; - } -} - -// Called after the unit test ends. -void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, - int /*iteration*/) { - FILE* xmlout = OpenFileForWriting(output_file_); - std::stringstream stream; - PrintXmlUnitTest(&stream, unit_test); - fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); - fclose(xmlout); -} - -void XmlUnitTestResultPrinter::ListTestsMatchingFilter( - const std::vector& test_suites) { - FILE* xmlout = OpenFileForWriting(output_file_); - std::stringstream stream; - PrintXmlTestsList(&stream, test_suites); - fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); - fclose(xmlout); -} - -// Returns an XML-escaped copy of the input string str. If is_attribute -// is true, the text is meant to appear as an attribute value, and -// normalizable whitespace is preserved by replacing it with character -// references. -// -// Invalid XML characters in str, if any, are stripped from the output. -// It is expected that most, if not all, of the text processed by this -// module will consist of ordinary English text. -// If this module is ever modified to produce version 1.1 XML output, -// most invalid characters can be retained using character references. -std::string XmlUnitTestResultPrinter::EscapeXml( - const std::string& str, bool is_attribute) { - Message m; - - for (size_t i = 0; i < str.size(); ++i) { - const char ch = str[i]; - switch (ch) { - case '<': - m << "<"; - break; - case '>': - m << ">"; - break; - case '&': - m << "&"; - break; - case '\'': - if (is_attribute) - m << "'"; - else - m << '\''; - break; - case '"': - if (is_attribute) - m << """; - else - m << '"'; - break; - default: - if (IsValidXmlCharacter(ch)) { - if (is_attribute && IsNormalizableWhitespace(ch)) - m << "&#x" << String::FormatByte(static_cast(ch)) - << ";"; - else - m << ch; - } - break; - } - } - - return m.GetString(); -} - -// Returns the given string with all characters invalid in XML removed. -// Currently invalid characters are dropped from the string. An -// alternative is to replace them with certain characters such as . or ?. -std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters( - const std::string& str) { - std::string output; - output.reserve(str.size()); - for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) - if (IsValidXmlCharacter(*it)) - output.push_back(*it); - - return output; -} - -// The following routines generate an XML representation of a UnitTest -// object. -// GOOGLETEST_CM0009 DO NOT DELETE -// -// This is how Google Test concepts map to the DTD: -// -// <-- corresponds to a UnitTest object -// <-- corresponds to a TestSuite object -// <-- corresponds to a TestInfo object -// ... -// ... -// ... -// <-- individual assertion failures -// -// -// - -// Formats the given time in milliseconds as seconds. -std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { - ::std::stringstream ss; - ss << (static_cast(ms) * 1e-3); - return ss.str(); -} - -static bool PortableLocaltime(time_t seconds, struct tm* out) { -#if defined(_MSC_VER) - return localtime_s(out, &seconds) == 0; -#elif defined(__MINGW32__) || defined(__MINGW64__) - // MINGW provides neither localtime_r nor localtime_s, but uses - // Windows' localtime(), which has a thread-local tm buffer. - struct tm* tm_ptr = localtime(&seconds); // NOLINT - if (tm_ptr == nullptr) return false; - *out = *tm_ptr; - return true; -#else - return localtime_r(&seconds, out) != nullptr; -#endif -} - -// Converts the given epoch time in milliseconds to a date string in the ISO -// 8601 format, without the timezone information. -std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { - struct tm time_struct; - if (!PortableLocaltime(static_cast(ms / 1000), &time_struct)) - return ""; - // YYYY-MM-DDThh:mm:ss - return StreamableToString(time_struct.tm_year + 1900) + "-" + - String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + - String::FormatIntWidth2(time_struct.tm_mday) + "T" + - String::FormatIntWidth2(time_struct.tm_hour) + ":" + - String::FormatIntWidth2(time_struct.tm_min) + ":" + - String::FormatIntWidth2(time_struct.tm_sec); -} - -// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. -void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, - const char* data) { - const char* segment = data; - *stream << ""); - if (next_segment != nullptr) { - stream->write( - segment, static_cast(next_segment - segment)); - *stream << "]]>]]>"); - } else { - *stream << segment; - break; - } - } - *stream << "]]>"; -} - -void XmlUnitTestResultPrinter::OutputXmlAttribute( - std::ostream* stream, - const std::string& element_name, - const std::string& name, - const std::string& value) { - const std::vector& allowed_names = - GetReservedOutputAttributesForElement(element_name); - - GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != - allowed_names.end()) - << "Attribute " << name << " is not allowed for element <" << element_name - << ">."; - - *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\""; -} - -// Prints an XML representation of a TestInfo object. -void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, - const char* test_suite_name, - const TestInfo& test_info) { - const TestResult& result = *test_info.result(); - const std::string kTestsuite = "testcase"; - - if (test_info.is_in_another_shard()) { - return; - } - - *stream << " \n"; - return; - } - - OutputXmlAttribute(stream, kTestsuite, "status", - test_info.should_run() ? "run" : "notrun"); - OutputXmlAttribute(stream, kTestsuite, "result", - test_info.should_run() - ? (result.Skipped() ? "skipped" : "completed") - : "suppressed"); - OutputXmlAttribute(stream, kTestsuite, "time", - FormatTimeInMillisAsSeconds(result.elapsed_time())); - OutputXmlAttribute(stream, kTestsuite, "classname", test_suite_name); - - int failures = 0; - for (int i = 0; i < result.total_part_count(); ++i) { - const TestPartResult& part = result.GetTestPartResult(i); - if (part.failed()) { - if (++failures == 1) { - *stream << ">\n"; - } - const std::string location = - internal::FormatCompilerIndependentFileLocation(part.file_name(), - part.line_number()); - const std::string summary = location + "\n" + part.summary(); - *stream << " "; - const std::string detail = location + "\n" + part.message(); - OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); - *stream << "\n"; - } - } - - if (failures == 0 && result.test_property_count() == 0) { - *stream << " />\n"; - } else { - if (failures == 0) { - *stream << ">\n"; - } - OutputXmlTestProperties(stream, result); - *stream << " \n"; - } -} - -// Prints an XML representation of a TestSuite object -void XmlUnitTestResultPrinter::PrintXmlTestSuite(std::ostream* stream, - const TestSuite& test_suite) { - const std::string kTestsuite = "testsuite"; - *stream << " <" << kTestsuite; - OutputXmlAttribute(stream, kTestsuite, "name", test_suite.name()); - OutputXmlAttribute(stream, kTestsuite, "tests", - StreamableToString(test_suite.reportable_test_count())); - if (!GTEST_FLAG(list_tests)) { - OutputXmlAttribute(stream, kTestsuite, "failures", - StreamableToString(test_suite.failed_test_count())); - OutputXmlAttribute( - stream, kTestsuite, "disabled", - StreamableToString(test_suite.reportable_disabled_test_count())); - OutputXmlAttribute(stream, kTestsuite, "errors", "0"); - OutputXmlAttribute(stream, kTestsuite, "time", - FormatTimeInMillisAsSeconds(test_suite.elapsed_time())); - *stream << TestPropertiesAsXmlAttributes(test_suite.ad_hoc_test_result()); - } - *stream << ">\n"; - for (int i = 0; i < test_suite.total_test_count(); ++i) { - if (test_suite.GetTestInfo(i)->is_reportable()) - OutputXmlTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); - } - *stream << " \n"; -} - -// Prints an XML summary of unit_test to output stream out. -void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream, - const UnitTest& unit_test) { - const std::string kTestsuites = "testsuites"; - - *stream << "\n"; - *stream << "<" << kTestsuites; - - OutputXmlAttribute(stream, kTestsuites, "tests", - StreamableToString(unit_test.reportable_test_count())); - OutputXmlAttribute(stream, kTestsuites, "failures", - StreamableToString(unit_test.failed_test_count())); - OutputXmlAttribute( - stream, kTestsuites, "disabled", - StreamableToString(unit_test.reportable_disabled_test_count())); - OutputXmlAttribute(stream, kTestsuites, "errors", "0"); - OutputXmlAttribute( - stream, kTestsuites, "timestamp", - FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp())); - OutputXmlAttribute(stream, kTestsuites, "time", - FormatTimeInMillisAsSeconds(unit_test.elapsed_time())); - - if (GTEST_FLAG(shuffle)) { - OutputXmlAttribute(stream, kTestsuites, "random_seed", - StreamableToString(unit_test.random_seed())); - } - *stream << TestPropertiesAsXmlAttributes(unit_test.ad_hoc_test_result()); - - OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); - *stream << ">\n"; - - for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { - if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) - PrintXmlTestSuite(stream, *unit_test.GetTestSuite(i)); - } - *stream << "\n"; -} - -void XmlUnitTestResultPrinter::PrintXmlTestsList( - std::ostream* stream, const std::vector& test_suites) { - const std::string kTestsuites = "testsuites"; - - *stream << "\n"; - *stream << "<" << kTestsuites; - - int total_tests = 0; - for (auto test_suite : test_suites) { - total_tests += test_suite->total_test_count(); - } - OutputXmlAttribute(stream, kTestsuites, "tests", - StreamableToString(total_tests)); - OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); - *stream << ">\n"; - - for (auto test_suite : test_suites) { - PrintXmlTestSuite(stream, *test_suite); - } - *stream << "\n"; -} - -// Produces a string representing the test properties in a result as space -// delimited XML attributes based on the property key="value" pairs. -std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( - const TestResult& result) { - Message attributes; - for (int i = 0; i < result.test_property_count(); ++i) { - const TestProperty& property = result.GetTestProperty(i); - attributes << " " << property.key() << "=" - << "\"" << EscapeXmlAttribute(property.value()) << "\""; - } - return attributes.GetString(); -} - -void XmlUnitTestResultPrinter::OutputXmlTestProperties( - std::ostream* stream, const TestResult& result) { - const std::string kProperties = "properties"; - const std::string kProperty = "property"; - - if (result.test_property_count() <= 0) { - return; - } - - *stream << "<" << kProperties << ">\n"; - for (int i = 0; i < result.test_property_count(); ++i) { - const TestProperty& property = result.GetTestProperty(i); - *stream << "<" << kProperty; - *stream << " name=\"" << EscapeXmlAttribute(property.key()) << "\""; - *stream << " value=\"" << EscapeXmlAttribute(property.value()) << "\""; - *stream << "/>\n"; - } - *stream << "\n"; -} - -// End XmlUnitTestResultPrinter - -// This class generates an JSON output file. -class JsonUnitTestResultPrinter : public EmptyTestEventListener { - public: - explicit JsonUnitTestResultPrinter(const char* output_file); - - void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override; - - // Prints an JSON summary of all unit tests. - static void PrintJsonTestList(::std::ostream* stream, - const std::vector& test_suites); - - private: - // Returns an JSON-escaped copy of the input string str. - static std::string EscapeJson(const std::string& str); - - //// Verifies that the given attribute belongs to the given element and - //// streams the attribute as JSON. - static void OutputJsonKey(std::ostream* stream, - const std::string& element_name, - const std::string& name, - const std::string& value, - const std::string& indent, - bool comma = true); - static void OutputJsonKey(std::ostream* stream, - const std::string& element_name, - const std::string& name, - int value, - const std::string& indent, - bool comma = true); - - // Streams a JSON representation of a TestInfo object. - static void OutputJsonTestInfo(::std::ostream* stream, - const char* test_suite_name, - const TestInfo& test_info); - - // Prints a JSON representation of a TestSuite object - static void PrintJsonTestSuite(::std::ostream* stream, - const TestSuite& test_suite); - - // Prints a JSON summary of unit_test to output stream out. - static void PrintJsonUnitTest(::std::ostream* stream, - const UnitTest& unit_test); - - // Produces a string representing the test properties in a result as - // a JSON dictionary. - static std::string TestPropertiesAsJson(const TestResult& result, - const std::string& indent); - - // The output file. - const std::string output_file_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(JsonUnitTestResultPrinter); -}; - -// Creates a new JsonUnitTestResultPrinter. -JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file) - : output_file_(output_file) { - if (output_file_.empty()) { - GTEST_LOG_(FATAL) << "JSON output file may not be null"; - } -} - -void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, - int /*iteration*/) { - FILE* jsonout = OpenFileForWriting(output_file_); - std::stringstream stream; - PrintJsonUnitTest(&stream, unit_test); - fprintf(jsonout, "%s", StringStreamToString(&stream).c_str()); - fclose(jsonout); -} - -// Returns an JSON-escaped copy of the input string str. -std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) { - Message m; - - for (size_t i = 0; i < str.size(); ++i) { - const char ch = str[i]; - switch (ch) { - case '\\': - case '"': - case '/': - m << '\\' << ch; - break; - case '\b': - m << "\\b"; - break; - case '\t': - m << "\\t"; - break; - case '\n': - m << "\\n"; - break; - case '\f': - m << "\\f"; - break; - case '\r': - m << "\\r"; - break; - default: - if (ch < ' ') { - m << "\\u00" << String::FormatByte(static_cast(ch)); - } else { - m << ch; - } - break; - } - } - - return m.GetString(); -} - -// The following routines generate an JSON representation of a UnitTest -// object. - -// Formats the given time in milliseconds as seconds. -static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) { - ::std::stringstream ss; - ss << (static_cast(ms) * 1e-3) << "s"; - return ss.str(); -} - -// Converts the given epoch time in milliseconds to a date string in the -// RFC3339 format, without the timezone information. -static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) { - struct tm time_struct; - if (!PortableLocaltime(static_cast(ms / 1000), &time_struct)) - return ""; - // YYYY-MM-DDThh:mm:ss - return StreamableToString(time_struct.tm_year + 1900) + "-" + - String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" + - String::FormatIntWidth2(time_struct.tm_mday) + "T" + - String::FormatIntWidth2(time_struct.tm_hour) + ":" + - String::FormatIntWidth2(time_struct.tm_min) + ":" + - String::FormatIntWidth2(time_struct.tm_sec) + "Z"; -} - -static inline std::string Indent(size_t width) { - return std::string(width, ' '); -} - -void JsonUnitTestResultPrinter::OutputJsonKey( - std::ostream* stream, - const std::string& element_name, - const std::string& name, - const std::string& value, - const std::string& indent, - bool comma) { - const std::vector& allowed_names = - GetReservedOutputAttributesForElement(element_name); - - GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != - allowed_names.end()) - << "Key \"" << name << "\" is not allowed for value \"" << element_name - << "\"."; - - *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\""; - if (comma) - *stream << ",\n"; -} - -void JsonUnitTestResultPrinter::OutputJsonKey( - std::ostream* stream, - const std::string& element_name, - const std::string& name, - int value, - const std::string& indent, - bool comma) { - const std::vector& allowed_names = - GetReservedOutputAttributesForElement(element_name); - - GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != - allowed_names.end()) - << "Key \"" << name << "\" is not allowed for value \"" << element_name - << "\"."; - - *stream << indent << "\"" << name << "\": " << StreamableToString(value); - if (comma) - *stream << ",\n"; -} - -// Prints a JSON representation of a TestInfo object. -void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream, - const char* test_suite_name, - const TestInfo& test_info) { - const TestResult& result = *test_info.result(); - const std::string kTestsuite = "testcase"; - const std::string kIndent = Indent(10); - - *stream << Indent(8) << "{\n"; - OutputJsonKey(stream, kTestsuite, "name", test_info.name(), kIndent); - - if (test_info.value_param() != nullptr) { - OutputJsonKey(stream, kTestsuite, "value_param", test_info.value_param(), - kIndent); - } - if (test_info.type_param() != nullptr) { - OutputJsonKey(stream, kTestsuite, "type_param", test_info.type_param(), - kIndent); - } - if (GTEST_FLAG(list_tests)) { - OutputJsonKey(stream, kTestsuite, "file", test_info.file(), kIndent); - OutputJsonKey(stream, kTestsuite, "line", test_info.line(), kIndent, false); - *stream << "\n" << Indent(8) << "}"; - return; - } - - OutputJsonKey(stream, kTestsuite, "status", - test_info.should_run() ? "RUN" : "NOTRUN", kIndent); - OutputJsonKey(stream, kTestsuite, "result", - test_info.should_run() - ? (result.Skipped() ? "SKIPPED" : "COMPLETED") - : "SUPPRESSED", - kIndent); - OutputJsonKey(stream, kTestsuite, "time", - FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent); - OutputJsonKey(stream, kTestsuite, "classname", test_suite_name, kIndent, - false); - *stream << TestPropertiesAsJson(result, kIndent); - - int failures = 0; - for (int i = 0; i < result.total_part_count(); ++i) { - const TestPartResult& part = result.GetTestPartResult(i); - if (part.failed()) { - *stream << ",\n"; - if (++failures == 1) { - *stream << kIndent << "\"" << "failures" << "\": [\n"; - } - const std::string location = - internal::FormatCompilerIndependentFileLocation(part.file_name(), - part.line_number()); - const std::string message = EscapeJson(location + "\n" + part.message()); - *stream << kIndent << " {\n" - << kIndent << " \"failure\": \"" << message << "\",\n" - << kIndent << " \"type\": \"\"\n" - << kIndent << " }"; - } - } - - if (failures > 0) - *stream << "\n" << kIndent << "]"; - *stream << "\n" << Indent(8) << "}"; -} - -// Prints an JSON representation of a TestSuite object -void JsonUnitTestResultPrinter::PrintJsonTestSuite( - std::ostream* stream, const TestSuite& test_suite) { - const std::string kTestsuite = "testsuite"; - const std::string kIndent = Indent(6); - - *stream << Indent(4) << "{\n"; - OutputJsonKey(stream, kTestsuite, "name", test_suite.name(), kIndent); - OutputJsonKey(stream, kTestsuite, "tests", test_suite.reportable_test_count(), - kIndent); - if (!GTEST_FLAG(list_tests)) { - OutputJsonKey(stream, kTestsuite, "failures", - test_suite.failed_test_count(), kIndent); - OutputJsonKey(stream, kTestsuite, "disabled", - test_suite.reportable_disabled_test_count(), kIndent); - OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent); - OutputJsonKey(stream, kTestsuite, "time", - FormatTimeInMillisAsDuration(test_suite.elapsed_time()), - kIndent, false); - *stream << TestPropertiesAsJson(test_suite.ad_hoc_test_result(), kIndent) - << ",\n"; - } - - *stream << kIndent << "\"" << kTestsuite << "\": [\n"; - - bool comma = false; - for (int i = 0; i < test_suite.total_test_count(); ++i) { - if (test_suite.GetTestInfo(i)->is_reportable()) { - if (comma) { - *stream << ",\n"; - } else { - comma = true; - } - OutputJsonTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i)); - } - } - *stream << "\n" << kIndent << "]\n" << Indent(4) << "}"; -} - -// Prints a JSON summary of unit_test to output stream out. -void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream, - const UnitTest& unit_test) { - const std::string kTestsuites = "testsuites"; - const std::string kIndent = Indent(2); - *stream << "{\n"; - - OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(), - kIndent); - OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(), - kIndent); - OutputJsonKey(stream, kTestsuites, "disabled", - unit_test.reportable_disabled_test_count(), kIndent); - OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent); - if (GTEST_FLAG(shuffle)) { - OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(), - kIndent); - } - OutputJsonKey(stream, kTestsuites, "timestamp", - FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()), - kIndent); - OutputJsonKey(stream, kTestsuites, "time", - FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent, - false); - - *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent) - << ",\n"; - - OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); - *stream << kIndent << "\"" << kTestsuites << "\": [\n"; - - bool comma = false; - for (int i = 0; i < unit_test.total_test_suite_count(); ++i) { - if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) { - if (comma) { - *stream << ",\n"; - } else { - comma = true; - } - PrintJsonTestSuite(stream, *unit_test.GetTestSuite(i)); - } - } - - *stream << "\n" << kIndent << "]\n" << "}\n"; -} - -void JsonUnitTestResultPrinter::PrintJsonTestList( - std::ostream* stream, const std::vector& test_suites) { - const std::string kTestsuites = "testsuites"; - const std::string kIndent = Indent(2); - *stream << "{\n"; - int total_tests = 0; - for (auto test_suite : test_suites) { - total_tests += test_suite->total_test_count(); - } - OutputJsonKey(stream, kTestsuites, "tests", total_tests, kIndent); - - OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent); - *stream << kIndent << "\"" << kTestsuites << "\": [\n"; - - for (size_t i = 0; i < test_suites.size(); ++i) { - if (i != 0) { - *stream << ",\n"; - } - PrintJsonTestSuite(stream, *test_suites[i]); - } - - *stream << "\n" - << kIndent << "]\n" - << "}\n"; -} -// Produces a string representing the test properties in a result as -// a JSON dictionary. -std::string JsonUnitTestResultPrinter::TestPropertiesAsJson( - const TestResult& result, const std::string& indent) { - Message attributes; - for (int i = 0; i < result.test_property_count(); ++i) { - const TestProperty& property = result.GetTestProperty(i); - attributes << ",\n" << indent << "\"" << property.key() << "\": " - << "\"" << EscapeJson(property.value()) << "\""; - } - return attributes.GetString(); -} - -// End JsonUnitTestResultPrinter - -#if GTEST_CAN_STREAM_RESULTS_ - -// Checks if str contains '=', '&', '%' or '\n' characters. If yes, -// replaces them by "%xx" where xx is their hexadecimal value. For -// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) -// in both time and space -- important as the input str may contain an -// arbitrarily long test failure message and stack trace. -std::string StreamingListener::UrlEncode(const char* str) { - std::string result; - result.reserve(strlen(str) + 1); - for (char ch = *str; ch != '\0'; ch = *++str) { - switch (ch) { - case '%': - case '=': - case '&': - case '\n': - result.append("%" + String::FormatByte(static_cast(ch))); - break; - default: - result.push_back(ch); - break; - } - } - return result; -} - -void StreamingListener::SocketWriter::MakeConnection() { - GTEST_CHECK_(sockfd_ == -1) - << "MakeConnection() can't be called when there is already a connection."; - - addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. - hints.ai_socktype = SOCK_STREAM; - addrinfo* servinfo = nullptr; - - // Use the getaddrinfo() to get a linked list of IP addresses for - // the given host name. - const int error_num = getaddrinfo( - host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); - if (error_num != 0) { - GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " - << gai_strerror(error_num); - } - - // Loop through all the results and connect to the first we can. - for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != nullptr; - cur_addr = cur_addr->ai_next) { - sockfd_ = socket( - cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol); - if (sockfd_ != -1) { - // Connect the client socket to the server socket. - if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { - close(sockfd_); - sockfd_ = -1; - } - } - } - - freeaddrinfo(servinfo); // all done with this structure - - if (sockfd_ == -1) { - GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " - << host_name_ << ":" << port_num_; - } -} - -// End of class Streaming Listener -#endif // GTEST_CAN_STREAM_RESULTS__ - -// class OsStackTraceGetter - -const char* const OsStackTraceGetterInterface::kElidedFramesMarker = - "... " GTEST_NAME_ " internal frames ..."; - -std::string OsStackTraceGetter::CurrentStackTrace(int max_depth, int skip_count) - GTEST_LOCK_EXCLUDED_(mutex_) { -#if GTEST_HAS_ABSL - std::string result; - - if (max_depth <= 0) { - return result; - } - - max_depth = std::min(max_depth, kMaxStackTraceDepth); - - std::vector raw_stack(max_depth); - // Skips the frames requested by the caller, plus this function. - const int raw_stack_size = - absl::GetStackTrace(&raw_stack[0], max_depth, skip_count + 1); - - void* caller_frame = nullptr; - { - MutexLock lock(&mutex_); - caller_frame = caller_frame_; - } - - for (int i = 0; i < raw_stack_size; ++i) { - if (raw_stack[i] == caller_frame && - !GTEST_FLAG(show_internal_stack_frames)) { - // Add a marker to the trace and stop adding frames. - absl::StrAppend(&result, kElidedFramesMarker, "\n"); - break; - } - - char tmp[1024]; - const char* symbol = "(unknown)"; - if (absl::Symbolize(raw_stack[i], tmp, sizeof(tmp))) { - symbol = tmp; - } - - char line[1024]; - snprintf(line, sizeof(line), " %p: %s\n", raw_stack[i], symbol); - result += line; - } - - return result; - -#else // !GTEST_HAS_ABSL - static_cast(max_depth); - static_cast(skip_count); - return ""; -#endif // GTEST_HAS_ABSL -} - -void OsStackTraceGetter::UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_) { -#if GTEST_HAS_ABSL - void* caller_frame = nullptr; - if (absl::GetStackTrace(&caller_frame, 1, 3) <= 0) { - caller_frame = nullptr; - } - - MutexLock lock(&mutex_); - caller_frame_ = caller_frame; -#endif // GTEST_HAS_ABSL -} - -// A helper class that creates the premature-exit file in its -// constructor and deletes the file in its destructor. -class ScopedPrematureExitFile { - public: - explicit ScopedPrematureExitFile(const char* premature_exit_filepath) - : premature_exit_filepath_(premature_exit_filepath ? - premature_exit_filepath : "") { - // If a path to the premature-exit file is specified... - if (!premature_exit_filepath_.empty()) { - // create the file with a single "0" character in it. I/O - // errors are ignored as there's nothing better we can do and we - // don't want to fail the test because of this. - FILE* pfile = posix::FOpen(premature_exit_filepath, "w"); - fwrite("0", 1, 1, pfile); - fclose(pfile); - } - } - - ~ScopedPrematureExitFile() { - if (!premature_exit_filepath_.empty()) { - int retval = remove(premature_exit_filepath_.c_str()); - if (retval) { - GTEST_LOG_(ERROR) << "Failed to remove premature exit filepath \"" - << premature_exit_filepath_ << "\" with error " - << retval; - } - } - } - - private: - const std::string premature_exit_filepath_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile); -}; - -} // namespace internal - -// class TestEventListeners - -TestEventListeners::TestEventListeners() - : repeater_(new internal::TestEventRepeater()), - default_result_printer_(nullptr), - default_xml_generator_(nullptr) {} - -TestEventListeners::~TestEventListeners() { delete repeater_; } - -// Returns the standard listener responsible for the default console -// output. Can be removed from the listeners list to shut down default -// console output. Note that removing this object from the listener list -// with Release transfers its ownership to the user. -void TestEventListeners::Append(TestEventListener* listener) { - repeater_->Append(listener); -} - -// Removes the given event listener from the list and returns it. It then -// becomes the caller's responsibility to delete the listener. Returns -// NULL if the listener is not found in the list. -TestEventListener* TestEventListeners::Release(TestEventListener* listener) { - if (listener == default_result_printer_) - default_result_printer_ = nullptr; - else if (listener == default_xml_generator_) - default_xml_generator_ = nullptr; - return repeater_->Release(listener); -} - -// Returns repeater that broadcasts the TestEventListener events to all -// subscribers. -TestEventListener* TestEventListeners::repeater() { return repeater_; } - -// Sets the default_result_printer attribute to the provided listener. -// The listener is also added to the listener list and previous -// default_result_printer is removed from it and deleted. The listener can -// also be NULL in which case it will not be added to the list. Does -// nothing if the previous and the current listener objects are the same. -void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { - if (default_result_printer_ != listener) { - // It is an error to pass this method a listener that is already in the - // list. - delete Release(default_result_printer_); - default_result_printer_ = listener; - if (listener != nullptr) Append(listener); - } -} - -// Sets the default_xml_generator attribute to the provided listener. The -// listener is also added to the listener list and previous -// default_xml_generator is removed from it and deleted. The listener can -// also be NULL in which case it will not be added to the list. Does -// nothing if the previous and the current listener objects are the same. -void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { - if (default_xml_generator_ != listener) { - // It is an error to pass this method a listener that is already in the - // list. - delete Release(default_xml_generator_); - default_xml_generator_ = listener; - if (listener != nullptr) Append(listener); - } -} - -// Controls whether events will be forwarded by the repeater to the -// listeners in the list. -bool TestEventListeners::EventForwardingEnabled() const { - return repeater_->forwarding_enabled(); -} - -void TestEventListeners::SuppressEventForwarding() { - repeater_->set_forwarding_enabled(false); -} - -// class UnitTest - -// Gets the singleton UnitTest object. The first time this method is -// called, a UnitTest object is constructed and returned. Consecutive -// calls will return the same object. -// -// We don't protect this under mutex_ as a user is not supposed to -// call this before main() starts, from which point on the return -// value will never change. -UnitTest* UnitTest::GetInstance() { - // CodeGear C++Builder insists on a public destructor for the - // default implementation. Use this implementation to keep good OO - // design with private destructor. - -#if defined(__BORLANDC__) - static UnitTest* const instance = new UnitTest; - return instance; -#else - static UnitTest instance; - return &instance; -#endif // defined(__BORLANDC__) -} - -// Gets the number of successful test suites. -int UnitTest::successful_test_suite_count() const { - return impl()->successful_test_suite_count(); -} - -// Gets the number of failed test suites. -int UnitTest::failed_test_suite_count() const { - return impl()->failed_test_suite_count(); -} - -// Gets the number of all test suites. -int UnitTest::total_test_suite_count() const { - return impl()->total_test_suite_count(); -} - -// Gets the number of all test suites that contain at least one test -// that should run. -int UnitTest::test_suite_to_run_count() const { - return impl()->test_suite_to_run_count(); -} - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -int UnitTest::successful_test_case_count() const { - return impl()->successful_test_suite_count(); -} -int UnitTest::failed_test_case_count() const { - return impl()->failed_test_suite_count(); -} -int UnitTest::total_test_case_count() const { - return impl()->total_test_suite_count(); -} -int UnitTest::test_case_to_run_count() const { - return impl()->test_suite_to_run_count(); -} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -// Gets the number of successful tests. -int UnitTest::successful_test_count() const { - return impl()->successful_test_count(); -} - -// Gets the number of skipped tests. -int UnitTest::skipped_test_count() const { - return impl()->skipped_test_count(); -} - -// Gets the number of failed tests. -int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } - -// Gets the number of disabled tests that will be reported in the XML report. -int UnitTest::reportable_disabled_test_count() const { - return impl()->reportable_disabled_test_count(); -} - -// Gets the number of disabled tests. -int UnitTest::disabled_test_count() const { - return impl()->disabled_test_count(); -} - -// Gets the number of tests to be printed in the XML report. -int UnitTest::reportable_test_count() const { - return impl()->reportable_test_count(); -} - -// Gets the number of all tests. -int UnitTest::total_test_count() const { return impl()->total_test_count(); } - -// Gets the number of tests that should run. -int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } - -// Gets the time of the test program start, in ms from the start of the -// UNIX epoch. -internal::TimeInMillis UnitTest::start_timestamp() const { - return impl()->start_timestamp(); -} - -// Gets the elapsed time, in milliseconds. -internal::TimeInMillis UnitTest::elapsed_time() const { - return impl()->elapsed_time(); -} - -// Returns true iff the unit test passed (i.e. all test suites passed). -bool UnitTest::Passed() const { return impl()->Passed(); } - -// Returns true iff the unit test failed (i.e. some test suite failed -// or something outside of all tests failed). -bool UnitTest::Failed() const { return impl()->Failed(); } - -// Gets the i-th test suite among all the test suites. i can range from 0 to -// total_test_suite_count() - 1. If i is not in that range, returns NULL. -const TestSuite* UnitTest::GetTestSuite(int i) const { - return impl()->GetTestSuite(i); -} - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -const TestCase* UnitTest::GetTestCase(int i) const { - return impl()->GetTestCase(i); -} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -// Returns the TestResult containing information on test failures and -// properties logged outside of individual test suites. -const TestResult& UnitTest::ad_hoc_test_result() const { - return *impl()->ad_hoc_test_result(); -} - -// Gets the i-th test suite among all the test suites. i can range from 0 to -// total_test_suite_count() - 1. If i is not in that range, returns NULL. -TestSuite* UnitTest::GetMutableTestSuite(int i) { - return impl()->GetMutableSuiteCase(i); -} - -// Returns the list of event listeners that can be used to track events -// inside Google Test. -TestEventListeners& UnitTest::listeners() { - return *impl()->listeners(); -} - -// Registers and returns a global test environment. When a test -// program is run, all global test environments will be set-up in the -// order they were registered. After all tests in the program have -// finished, all global test environments will be torn-down in the -// *reverse* order they were registered. -// -// The UnitTest object takes ownership of the given environment. -// -// We don't protect this under mutex_, as we only support calling it -// from the main thread. -Environment* UnitTest::AddEnvironment(Environment* env) { - if (env == nullptr) { - return nullptr; - } - - impl_->environments().push_back(env); - return env; -} - -// Adds a TestPartResult to the current TestResult object. All Google Test -// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call -// this to report their results. The user code should use the -// assertion macros instead of calling this directly. -void UnitTest::AddTestPartResult( - TestPartResult::Type result_type, - const char* file_name, - int line_number, - const std::string& message, - const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) { - Message msg; - msg << message; - - internal::MutexLock lock(&mutex_); - if (impl_->gtest_trace_stack().size() > 0) { - msg << "\n" << GTEST_NAME_ << " trace:"; - - for (size_t i = impl_->gtest_trace_stack().size(); i > 0; --i) { - const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; - msg << "\n" << internal::FormatFileLocation(trace.file, trace.line) - << " " << trace.message; - } - } - - if (os_stack_trace.c_str() != nullptr && !os_stack_trace.empty()) { - msg << internal::kStackTraceMarker << os_stack_trace; - } - - const TestPartResult result = TestPartResult( - result_type, file_name, line_number, msg.GetString().c_str()); - impl_->GetTestPartResultReporterForCurrentThread()-> - ReportTestPartResult(result); - - if (result_type != TestPartResult::kSuccess && - result_type != TestPartResult::kSkip) { - // gtest_break_on_failure takes precedence over - // gtest_throw_on_failure. This allows a user to set the latter - // in the code (perhaps in order to use Google Test assertions - // with another testing framework) and specify the former on the - // command line for debugging. - if (GTEST_FLAG(break_on_failure)) { -#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT - // Using DebugBreak on Windows allows gtest to still break into a debugger - // when a failure happens and both the --gtest_break_on_failure and - // the --gtest_catch_exceptions flags are specified. - DebugBreak(); -#elif (!defined(__native_client__)) && \ - ((defined(__clang__) || defined(__GNUC__)) && \ - (defined(__x86_64__) || defined(__i386__))) - // with clang/gcc we can achieve the same effect on x86 by invoking int3 - asm("int3"); -#else - // Dereference nullptr through a volatile pointer to prevent the compiler - // from removing. We use this rather than abort() or __builtin_trap() for - // portability: some debuggers don't correctly trap abort(). - *static_cast(nullptr) = 1; -#endif // GTEST_OS_WINDOWS - } else if (GTEST_FLAG(throw_on_failure)) { -#if GTEST_HAS_EXCEPTIONS - throw internal::GoogleTestFailureException(result); -#else - // We cannot call abort() as it generates a pop-up in debug mode - // that cannot be suppressed in VC 7.1 or below. - exit(1); -#endif - } - } -} - -// Adds a TestProperty to the current TestResult object when invoked from -// inside a test, to current TestSuite's ad_hoc_test_result_ when invoked -// from SetUpTestSuite or TearDownTestSuite, or to the global property set -// when invoked elsewhere. If the result already contains a property with -// the same key, the value will be updated. -void UnitTest::RecordProperty(const std::string& key, - const std::string& value) { - impl_->RecordProperty(TestProperty(key, value)); -} - -// Runs all tests in this UnitTest object and prints the result. -// Returns 0 if successful, or 1 otherwise. -// -// We don't protect this under mutex_, as we only support calling it -// from the main thread. -int UnitTest::Run() { - const bool in_death_test_child_process = - internal::GTEST_FLAG(internal_run_death_test).length() > 0; - - // Google Test implements this protocol for catching that a test - // program exits before returning control to Google Test: - // - // 1. Upon start, Google Test creates a file whose absolute path - // is specified by the environment variable - // TEST_PREMATURE_EXIT_FILE. - // 2. When Google Test has finished its work, it deletes the file. - // - // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before - // running a Google-Test-based test program and check the existence - // of the file at the end of the test execution to see if it has - // exited prematurely. - - // If we are in the child process of a death test, don't - // create/delete the premature exit file, as doing so is unnecessary - // and will confuse the parent process. Otherwise, create/delete - // the file upon entering/leaving this function. If the program - // somehow exits before this function has a chance to return, the - // premature-exit file will be left undeleted, causing a test runner - // that understands the premature-exit-file protocol to report the - // test as having failed. - const internal::ScopedPrematureExitFile premature_exit_file( - in_death_test_child_process - ? nullptr - : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE")); - - // Captures the value of GTEST_FLAG(catch_exceptions). This value will be - // used for the duration of the program. - impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); - -#if GTEST_OS_WINDOWS - // Either the user wants Google Test to catch exceptions thrown by the - // tests or this is executing in the context of death test child - // process. In either case the user does not want to see pop-up dialogs - // about crashes - they are expected. - if (impl()->catch_exceptions() || in_death_test_child_process) { -# if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT - // SetErrorMode doesn't exist on CE. - SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | - SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); -# endif // !GTEST_OS_WINDOWS_MOBILE - -# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE - // Death test children can be terminated with _abort(). On Windows, - // _abort() can show a dialog with a warning message. This forces the - // abort message to go to stderr instead. - _set_error_mode(_OUT_TO_STDERR); -# endif - -# if defined(_MSC_VER) && !GTEST_OS_WINDOWS_MOBILE - // In the debug version, Visual Studio pops up a separate dialog - // offering a choice to debug the aborted program. We need to suppress - // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement - // executed. Google Test will notify the user of any unexpected - // failure via stderr. - if (!GTEST_FLAG(break_on_failure)) - _set_abort_behavior( - 0x0, // Clear the following flags: - _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. -# endif - } -#endif // GTEST_OS_WINDOWS - - return internal::HandleExceptionsInMethodIfSupported( - impl(), - &internal::UnitTestImpl::RunAllTests, - "auxiliary test code (environments or event listeners)") ? 0 : 1; -} - -// Returns the working directory when the first TEST() or TEST_F() was -// executed. -const char* UnitTest::original_working_dir() const { - return impl_->original_working_dir_.c_str(); -} - -// Returns the TestSuite object for the test that's currently running, -// or NULL if no test is running. -const TestSuite* UnitTest::current_test_suite() const - GTEST_LOCK_EXCLUDED_(mutex_) { - internal::MutexLock lock(&mutex_); - return impl_->current_test_suite(); -} - -// Legacy API is still available but deprecated -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -const TestCase* UnitTest::current_test_case() const - GTEST_LOCK_EXCLUDED_(mutex_) { - internal::MutexLock lock(&mutex_); - return impl_->current_test_suite(); -} -#endif - -// Returns the TestInfo object for the test that's currently running, -// or NULL if no test is running. -const TestInfo* UnitTest::current_test_info() const - GTEST_LOCK_EXCLUDED_(mutex_) { - internal::MutexLock lock(&mutex_); - return impl_->current_test_info(); -} - -// Returns the random seed used at the start of the current test run. -int UnitTest::random_seed() const { return impl_->random_seed(); } - -// Returns ParameterizedTestSuiteRegistry object used to keep track of -// value-parameterized tests and instantiate and register them. -internal::ParameterizedTestSuiteRegistry& -UnitTest::parameterized_test_registry() GTEST_LOCK_EXCLUDED_(mutex_) { - return impl_->parameterized_test_registry(); -} - -// Creates an empty UnitTest. -UnitTest::UnitTest() { - impl_ = new internal::UnitTestImpl(this); -} - -// Destructor of UnitTest. -UnitTest::~UnitTest() { - delete impl_; -} - -// Pushes a trace defined by SCOPED_TRACE() on to the per-thread -// Google Test trace stack. -void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) - GTEST_LOCK_EXCLUDED_(mutex_) { - internal::MutexLock lock(&mutex_); - impl_->gtest_trace_stack().push_back(trace); -} - -// Pops a trace from the per-thread Google Test trace stack. -void UnitTest::PopGTestTrace() - GTEST_LOCK_EXCLUDED_(mutex_) { - internal::MutexLock lock(&mutex_); - impl_->gtest_trace_stack().pop_back(); -} - -namespace internal { - -UnitTestImpl::UnitTestImpl(UnitTest* parent) - : parent_(parent), - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */) - default_global_test_part_result_reporter_(this), - default_per_thread_test_part_result_reporter_(this), - GTEST_DISABLE_MSC_WARNINGS_POP_() global_test_part_result_repoter_( - &default_global_test_part_result_reporter_), - per_thread_test_part_result_reporter_( - &default_per_thread_test_part_result_reporter_), - parameterized_test_registry_(), - parameterized_tests_registered_(false), - last_death_test_suite_(-1), - current_test_suite_(nullptr), - current_test_info_(nullptr), - ad_hoc_test_result_(), - os_stack_trace_getter_(nullptr), - post_flag_parse_init_performed_(false), - random_seed_(0), // Will be overridden by the flag before first use. - random_(0), // Will be reseeded before first use. - start_timestamp_(0), - elapsed_time_(0), -#if GTEST_HAS_DEATH_TEST - death_test_factory_(new DefaultDeathTestFactory), -#endif - // Will be overridden by the flag before first use. - catch_exceptions_(false) { - listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); -} - -UnitTestImpl::~UnitTestImpl() { - // Deletes every TestSuite. - ForEach(test_suites_, internal::Delete); - - // Deletes every Environment. - ForEach(environments_, internal::Delete); - - delete os_stack_trace_getter_; -} - -// Adds a TestProperty to the current TestResult object when invoked in a -// context of a test, to current test suite's ad_hoc_test_result when invoke -// from SetUpTestSuite/TearDownTestSuite, or to the global property set -// otherwise. If the result already contains a property with the same key, -// the value will be updated. -void UnitTestImpl::RecordProperty(const TestProperty& test_property) { - std::string xml_element; - TestResult* test_result; // TestResult appropriate for property recording. - - if (current_test_info_ != nullptr) { - xml_element = "testcase"; - test_result = &(current_test_info_->result_); - } else if (current_test_suite_ != nullptr) { - xml_element = "testsuite"; - test_result = &(current_test_suite_->ad_hoc_test_result_); - } else { - xml_element = "testsuites"; - test_result = &ad_hoc_test_result_; - } - test_result->RecordProperty(xml_element, test_property); -} - -#if GTEST_HAS_DEATH_TEST -// Disables event forwarding if the control is currently in a death test -// subprocess. Must not be called before InitGoogleTest. -void UnitTestImpl::SuppressTestEventsIfInSubprocess() { - if (internal_run_death_test_flag_.get() != nullptr) - listeners()->SuppressEventForwarding(); -} -#endif // GTEST_HAS_DEATH_TEST - -// Initializes event listeners performing XML output as specified by -// UnitTestOptions. Must not be called before InitGoogleTest. -void UnitTestImpl::ConfigureXmlOutput() { - const std::string& output_format = UnitTestOptions::GetOutputFormat(); - if (output_format == "xml") { - listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); - } else if (output_format == "json") { - listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); - } else if (output_format != "") { - GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \"" - << output_format << "\" ignored."; - } -} - -#if GTEST_CAN_STREAM_RESULTS_ -// Initializes event listeners for streaming test results in string form. -// Must not be called before InitGoogleTest. -void UnitTestImpl::ConfigureStreamingOutput() { - const std::string& target = GTEST_FLAG(stream_result_to); - if (!target.empty()) { - const size_t pos = target.find(':'); - if (pos != std::string::npos) { - listeners()->Append(new StreamingListener(target.substr(0, pos), - target.substr(pos+1))); - } else { - GTEST_LOG_(WARNING) << "unrecognized streaming target \"" << target - << "\" ignored."; - } - } -} -#endif // GTEST_CAN_STREAM_RESULTS_ - -// Performs initialization dependent upon flag values obtained in -// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to -// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest -// this function is also called from RunAllTests. Since this function can be -// called more than once, it has to be idempotent. -void UnitTestImpl::PostFlagParsingInit() { - // Ensures that this function does not execute more than once. - if (!post_flag_parse_init_performed_) { - post_flag_parse_init_performed_ = true; - -#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) - // Register to send notifications about key process state changes. - listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_()); -#endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) - -#if GTEST_HAS_DEATH_TEST - InitDeathTestSubprocessControlInfo(); - SuppressTestEventsIfInSubprocess(); -#endif // GTEST_HAS_DEATH_TEST - - // Registers parameterized tests. This makes parameterized tests - // available to the UnitTest reflection API without running - // RUN_ALL_TESTS. - RegisterParameterizedTests(); - - // Configures listeners for XML output. This makes it possible for users - // to shut down the default XML output before invoking RUN_ALL_TESTS. - ConfigureXmlOutput(); - -#if GTEST_CAN_STREAM_RESULTS_ - // Configures listeners for streaming test results to the specified server. - ConfigureStreamingOutput(); -#endif // GTEST_CAN_STREAM_RESULTS_ - -#if GTEST_HAS_ABSL - if (GTEST_FLAG(install_failure_signal_handler)) { - absl::FailureSignalHandlerOptions options; - absl::InstallFailureSignalHandler(options); - } -#endif // GTEST_HAS_ABSL - } -} - -// A predicate that checks the name of a TestSuite against a known -// value. -// -// This is used for implementation of the UnitTest class only. We put -// it in the anonymous namespace to prevent polluting the outer -// namespace. -// -// TestSuiteNameIs is copyable. -class TestSuiteNameIs { - public: - // Constructor. - explicit TestSuiteNameIs(const std::string& name) : name_(name) {} - - // Returns true iff the name of test_suite matches name_. - bool operator()(const TestSuite* test_suite) const { - return test_suite != nullptr && - strcmp(test_suite->name(), name_.c_str()) == 0; - } - - private: - std::string name_; -}; - -// Finds and returns a TestSuite with the given name. If one doesn't -// exist, creates one and returns it. It's the CALLER'S -// RESPONSIBILITY to ensure that this function is only called WHEN THE -// TESTS ARE NOT SHUFFLED. -// -// Arguments: -// -// test_suite_name: name of the test suite -// type_param: the name of the test suite's type parameter, or NULL if -// this is not a typed or a type-parameterized test suite. -// set_up_tc: pointer to the function that sets up the test suite -// tear_down_tc: pointer to the function that tears down the test suite -TestSuite* UnitTestImpl::GetTestSuite( - const char* test_suite_name, const char* type_param, - internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc) { - // Can we find a TestSuite with the given name? - const auto test_suite = - std::find_if(test_suites_.rbegin(), test_suites_.rend(), - TestSuiteNameIs(test_suite_name)); - - if (test_suite != test_suites_.rend()) return *test_suite; - - // No. Let's create one. - auto* const new_test_suite = - new TestSuite(test_suite_name, type_param, set_up_tc, tear_down_tc); - - // Is this a death test suite? - if (internal::UnitTestOptions::MatchesFilter(test_suite_name, - kDeathTestSuiteFilter)) { - // Yes. Inserts the test suite after the last death test suite - // defined so far. This only works when the test suites haven't - // been shuffled. Otherwise we may end up running a death test - // after a non-death test. - ++last_death_test_suite_; - test_suites_.insert(test_suites_.begin() + last_death_test_suite_, - new_test_suite); - } else { - // No. Appends to the end of the list. - test_suites_.push_back(new_test_suite); - } - - test_suite_indices_.push_back(static_cast(test_suite_indices_.size())); - return new_test_suite; -} - -// Helpers for setting up / tearing down the given environment. They -// are for use in the ForEach() function. -static void SetUpEnvironment(Environment* env) { env->SetUp(); } -static void TearDownEnvironment(Environment* env) { env->TearDown(); } - -// Runs all tests in this UnitTest object, prints the result, and -// returns true if all tests are successful. If any exception is -// thrown during a test, the test is considered to be failed, but the -// rest of the tests will still be run. -// -// When parameterized tests are enabled, it expands and registers -// parameterized tests first in RegisterParameterizedTests(). -// All other functions called from RunAllTests() may safely assume that -// parameterized tests are ready to be counted and run. -bool UnitTestImpl::RunAllTests() { - // True iff Google Test is initialized before RUN_ALL_TESTS() is called. - const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized(); - - // Do not run any test if the --help flag was specified. - if (g_help_flag) - return true; - - // Repeats the call to the post-flag parsing initialization in case the - // user didn't call InitGoogleTest. - PostFlagParsingInit(); - - // Even if sharding is not on, test runners may want to use the - // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding - // protocol. - internal::WriteToShardStatusFileIfNeeded(); - - // True iff we are in a subprocess for running a thread-safe-style - // death test. - bool in_subprocess_for_death_test = false; - -#if GTEST_HAS_DEATH_TEST - in_subprocess_for_death_test = - (internal_run_death_test_flag_.get() != nullptr); -# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) - if (in_subprocess_for_death_test) { - GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_(); - } -# endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) -#endif // GTEST_HAS_DEATH_TEST - - const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, - in_subprocess_for_death_test); - - // Compares the full test names with the filter to decide which - // tests to run. - const bool has_tests_to_run = FilterTests(should_shard - ? HONOR_SHARDING_PROTOCOL - : IGNORE_SHARDING_PROTOCOL) > 0; - - // Lists the tests and exits if the --gtest_list_tests flag was specified. - if (GTEST_FLAG(list_tests)) { - // This must be called *after* FilterTests() has been called. - ListTestsMatchingFilter(); - return true; - } - - random_seed_ = GTEST_FLAG(shuffle) ? - GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; - - // True iff at least one test has failed. - bool failed = false; - - TestEventListener* repeater = listeners()->repeater(); - - start_timestamp_ = GetTimeInMillis(); - repeater->OnTestProgramStart(*parent_); - - // How many times to repeat the tests? We don't want to repeat them - // when we are inside the subprocess of a death test. - const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); - // Repeats forever if the repeat count is negative. - const bool gtest_repeat_forever = repeat < 0; - for (int i = 0; gtest_repeat_forever || i != repeat; i++) { - // We want to preserve failures generated by ad-hoc test - // assertions executed before RUN_ALL_TESTS(). - ClearNonAdHocTestResult(); - - const TimeInMillis start = GetTimeInMillis(); - - // Shuffles test suites and tests if requested. - if (has_tests_to_run && GTEST_FLAG(shuffle)) { - random()->Reseed(static_cast(random_seed_)); - // This should be done before calling OnTestIterationStart(), - // such that a test event listener can see the actual test order - // in the event. - ShuffleTests(); - } - - // Tells the unit test event listeners that the tests are about to start. - repeater->OnTestIterationStart(*parent_, i); - - // Runs each test suite if there is at least one test to run. - if (has_tests_to_run) { - // Sets up all environments beforehand. - repeater->OnEnvironmentsSetUpStart(*parent_); - ForEach(environments_, SetUpEnvironment); - repeater->OnEnvironmentsSetUpEnd(*parent_); - - // Runs the tests only if there was no fatal failure or skip triggered - // during global set-up. - if (Test::IsSkipped()) { - // Emit diagnostics when global set-up calls skip, as it will not be - // emitted by default. - TestResult& test_result = - *internal::GetUnitTestImpl()->current_test_result(); - for (int j = 0; j < test_result.total_part_count(); ++j) { - const TestPartResult& test_part_result = - test_result.GetTestPartResult(j); - if (test_part_result.type() == TestPartResult::kSkip) { - const std::string& result = test_part_result.message(); - printf("%s\n", result.c_str()); - } - } - fflush(stdout); - } else if (!Test::HasFatalFailure()) { - for (int test_index = 0; test_index < total_test_suite_count(); - test_index++) { - GetMutableSuiteCase(test_index)->Run(); - } - } - - // Tears down all environments in reverse order afterwards. - repeater->OnEnvironmentsTearDownStart(*parent_); - std::for_each(environments_.rbegin(), environments_.rend(), - TearDownEnvironment); - repeater->OnEnvironmentsTearDownEnd(*parent_); - } - - elapsed_time_ = GetTimeInMillis() - start; - - // Tells the unit test event listener that the tests have just finished. - repeater->OnTestIterationEnd(*parent_, i); - - // Gets the result and clears it. - if (!Passed()) { - failed = true; - } - - // Restores the original test order after the iteration. This - // allows the user to quickly repro a failure that happens in the - // N-th iteration without repeating the first (N - 1) iterations. - // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in - // case the user somehow changes the value of the flag somewhere - // (it's always safe to unshuffle the tests). - UnshuffleTests(); - - if (GTEST_FLAG(shuffle)) { - // Picks a new random seed for each iteration. - random_seed_ = GetNextRandomSeed(random_seed_); - } - } - - repeater->OnTestProgramEnd(*parent_); - - if (!gtest_is_initialized_before_run_all_tests) { - ColoredPrintf( - COLOR_RED, - "\nIMPORTANT NOTICE - DO NOT IGNORE:\n" - "This test program did NOT call " GTEST_INIT_GOOGLE_TEST_NAME_ - "() before calling RUN_ALL_TESTS(). This is INVALID. Soon " GTEST_NAME_ - " will start to enforce the valid usage. " - "Please fix it ASAP, or IT WILL START TO FAIL.\n"); // NOLINT -#if GTEST_FOR_GOOGLE_ - ColoredPrintf(COLOR_RED, - "For more details, see http://wiki/Main/ValidGUnitMain.\n"); -#endif // GTEST_FOR_GOOGLE_ - } - - return !failed; -} - -// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file -// if the variable is present. If a file already exists at this location, this -// function will write over it. If the variable is present, but the file cannot -// be created, prints an error and exits. -void WriteToShardStatusFileIfNeeded() { - const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); - if (test_shard_file != nullptr) { - FILE* const file = posix::FOpen(test_shard_file, "w"); - if (file == nullptr) { - ColoredPrintf(COLOR_RED, - "Could not write to the test shard status file \"%s\" " - "specified by the %s environment variable.\n", - test_shard_file, kTestShardStatusFile); - fflush(stdout); - exit(EXIT_FAILURE); - } - fclose(file); - } -} - -// Checks whether sharding is enabled by examining the relevant -// environment variable values. If the variables are present, -// but inconsistent (i.e., shard_index >= total_shards), prints -// an error and exits. If in_subprocess_for_death_test, sharding is -// disabled because it must only be applied to the original test -// process. Otherwise, we could filter out death tests we intended to execute. -bool ShouldShard(const char* total_shards_env, - const char* shard_index_env, - bool in_subprocess_for_death_test) { - if (in_subprocess_for_death_test) { - return false; - } - - const Int32 total_shards = Int32FromEnvOrDie(total_shards_env, -1); - const Int32 shard_index = Int32FromEnvOrDie(shard_index_env, -1); - - if (total_shards == -1 && shard_index == -1) { - return false; - } else if (total_shards == -1 && shard_index != -1) { - const Message msg = Message() - << "Invalid environment variables: you have " - << kTestShardIndex << " = " << shard_index - << ", but have left " << kTestTotalShards << " unset.\n"; - ColoredPrintf(COLOR_RED, "%s", msg.GetString().c_str()); - fflush(stdout); - exit(EXIT_FAILURE); - } else if (total_shards != -1 && shard_index == -1) { - const Message msg = Message() - << "Invalid environment variables: you have " - << kTestTotalShards << " = " << total_shards - << ", but have left " << kTestShardIndex << " unset.\n"; - ColoredPrintf(COLOR_RED, "%s", msg.GetString().c_str()); - fflush(stdout); - exit(EXIT_FAILURE); - } else if (shard_index < 0 || shard_index >= total_shards) { - const Message msg = Message() - << "Invalid environment variables: we require 0 <= " - << kTestShardIndex << " < " << kTestTotalShards - << ", but you have " << kTestShardIndex << "=" << shard_index - << ", " << kTestTotalShards << "=" << total_shards << ".\n"; - ColoredPrintf(COLOR_RED, "%s", msg.GetString().c_str()); - fflush(stdout); - exit(EXIT_FAILURE); - } - - return total_shards > 1; -} - -// Parses the environment variable var as an Int32. If it is unset, -// returns default_val. If it is not an Int32, prints an error -// and aborts. -Int32 Int32FromEnvOrDie(const char* var, Int32 default_val) { - const char* str_val = posix::GetEnv(var); - if (str_val == nullptr) { - return default_val; - } - - Int32 result; - if (!ParseInt32(Message() << "The value of environment variable " << var, - str_val, &result)) { - exit(EXIT_FAILURE); - } - return result; -} - -// Given the total number of shards, the shard index, and the test id, -// returns true iff the test should be run on this shard. The test id is -// some arbitrary but unique non-negative integer assigned to each test -// method. Assumes that 0 <= shard_index < total_shards. -bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { - return (test_id % total_shards) == shard_index; -} - -// Compares the name of each test with the user-specified filter to -// decide whether the test should be run, then records the result in -// each TestSuite and TestInfo object. -// If shard_tests == true, further filters tests based on sharding -// variables in the environment - see -// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md -// . Returns the number of tests that should run. -int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { - const Int32 total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ? - Int32FromEnvOrDie(kTestTotalShards, -1) : -1; - const Int32 shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ? - Int32FromEnvOrDie(kTestShardIndex, -1) : -1; - - // num_runnable_tests are the number of tests that will - // run across all shards (i.e., match filter and are not disabled). - // num_selected_tests are the number of tests to be run on - // this shard. - int num_runnable_tests = 0; - int num_selected_tests = 0; - for (auto* test_suite : test_suites_) { - const std::string& test_suite_name = test_suite->name(); - test_suite->set_should_run(false); - - for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { - TestInfo* const test_info = test_suite->test_info_list()[j]; - const std::string test_name(test_info->name()); - // A test is disabled if test suite name or test name matches - // kDisableTestFilter. - const bool is_disabled = internal::UnitTestOptions::MatchesFilter( - test_suite_name, kDisableTestFilter) || - internal::UnitTestOptions::MatchesFilter( - test_name, kDisableTestFilter); - test_info->is_disabled_ = is_disabled; - - const bool matches_filter = internal::UnitTestOptions::FilterMatchesTest( - test_suite_name, test_name); - test_info->matches_filter_ = matches_filter; - - const bool is_runnable = - (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && - matches_filter; - - const bool is_in_another_shard = - shard_tests != IGNORE_SHARDING_PROTOCOL && - !ShouldRunTestOnShard(total_shards, shard_index, num_runnable_tests); - test_info->is_in_another_shard_ = is_in_another_shard; - const bool is_selected = is_runnable && !is_in_another_shard; - - num_runnable_tests += is_runnable; - num_selected_tests += is_selected; - - test_info->should_run_ = is_selected; - test_suite->set_should_run(test_suite->should_run() || is_selected); - } - } - return num_selected_tests; -} - -// Prints the given C-string on a single line by replacing all '\n' -// characters with string "\\n". If the output takes more than -// max_length characters, only prints the first max_length characters -// and "...". -static void PrintOnOneLine(const char* str, int max_length) { - if (str != nullptr) { - for (int i = 0; *str != '\0'; ++str) { - if (i >= max_length) { - printf("..."); - break; - } - if (*str == '\n') { - printf("\\n"); - i += 2; - } else { - printf("%c", *str); - ++i; - } - } - } -} - -// Prints the names of the tests matching the user-specified filter flag. -void UnitTestImpl::ListTestsMatchingFilter() { - // Print at most this many characters for each type/value parameter. - const int kMaxParamLength = 250; - - for (auto* test_suite : test_suites_) { - bool printed_test_suite_name = false; - - for (size_t j = 0; j < test_suite->test_info_list().size(); j++) { - const TestInfo* const test_info = test_suite->test_info_list()[j]; - if (test_info->matches_filter_) { - if (!printed_test_suite_name) { - printed_test_suite_name = true; - printf("%s.", test_suite->name()); - if (test_suite->type_param() != nullptr) { - printf(" # %s = ", kTypeParamLabel); - // We print the type parameter on a single line to make - // the output easy to parse by a program. - PrintOnOneLine(test_suite->type_param(), kMaxParamLength); - } - printf("\n"); - } - printf(" %s", test_info->name()); - if (test_info->value_param() != nullptr) { - printf(" # %s = ", kValueParamLabel); - // We print the value parameter on a single line to make the - // output easy to parse by a program. - PrintOnOneLine(test_info->value_param(), kMaxParamLength); - } - printf("\n"); - } - } - } - fflush(stdout); - const std::string& output_format = UnitTestOptions::GetOutputFormat(); - if (output_format == "xml" || output_format == "json") { - FILE* fileout = OpenFileForWriting( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str()); - std::stringstream stream; - if (output_format == "xml") { - XmlUnitTestResultPrinter( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) - .PrintXmlTestsList(&stream, test_suites_); - } else if (output_format == "json") { - JsonUnitTestResultPrinter( - UnitTestOptions::GetAbsolutePathToOutputFile().c_str()) - .PrintJsonTestList(&stream, test_suites_); - } - fprintf(fileout, "%s", StringStreamToString(&stream).c_str()); - fclose(fileout); - } -} - -// Sets the OS stack trace getter. -// -// Does nothing if the input and the current OS stack trace getter are -// the same; otherwise, deletes the old getter and makes the input the -// current getter. -void UnitTestImpl::set_os_stack_trace_getter( - OsStackTraceGetterInterface* getter) { - if (os_stack_trace_getter_ != getter) { - delete os_stack_trace_getter_; - os_stack_trace_getter_ = getter; - } -} - -// Returns the current OS stack trace getter if it is not NULL; -// otherwise, creates an OsStackTraceGetter, makes it the current -// getter, and returns it. -OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { - if (os_stack_trace_getter_ == nullptr) { -#ifdef GTEST_OS_STACK_TRACE_GETTER_ - os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_; -#else - os_stack_trace_getter_ = new OsStackTraceGetter; -#endif // GTEST_OS_STACK_TRACE_GETTER_ - } - - return os_stack_trace_getter_; -} - -// Returns the most specific TestResult currently running. -TestResult* UnitTestImpl::current_test_result() { - if (current_test_info_ != nullptr) { - return ¤t_test_info_->result_; - } - if (current_test_suite_ != nullptr) { - return ¤t_test_suite_->ad_hoc_test_result_; - } - return &ad_hoc_test_result_; -} - -// Shuffles all test suites, and the tests within each test suite, -// making sure that death tests are still run first. -void UnitTestImpl::ShuffleTests() { - // Shuffles the death test suites. - ShuffleRange(random(), 0, last_death_test_suite_ + 1, &test_suite_indices_); - - // Shuffles the non-death test suites. - ShuffleRange(random(), last_death_test_suite_ + 1, - static_cast(test_suites_.size()), &test_suite_indices_); - - // Shuffles the tests inside each test suite. - for (auto& test_suite : test_suites_) { - test_suite->ShuffleTests(random()); - } -} - -// Restores the test suites and tests to their order before the first shuffle. -void UnitTestImpl::UnshuffleTests() { - for (size_t i = 0; i < test_suites_.size(); i++) { - // Unshuffles the tests in each test suite. - test_suites_[i]->UnshuffleTests(); - // Resets the index of each test suite. - test_suite_indices_[i] = static_cast(i); - } -} - -// Returns the current OS stack trace as an std::string. -// -// The maximum number of stack frames to be included is specified by -// the gtest_stack_trace_depth flag. The skip_count parameter -// specifies the number of top frames to be skipped, which doesn't -// count against the number of frames to be included. -// -// For example, if Foo() calls Bar(), which in turn calls -// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in -// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. -std::string GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, - int skip_count) { - // We pass skip_count + 1 to skip this wrapper function in addition - // to what the user really wants to skip. - return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); -} - -// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to -// suppress unreachable code warnings. -namespace { -class ClassUniqueToAlwaysTrue {}; -} - -bool IsTrue(bool condition) { return condition; } - -bool AlwaysTrue() { -#if GTEST_HAS_EXCEPTIONS - // This condition is always false so AlwaysTrue() never actually throws, - // but it makes the compiler think that it may throw. - if (IsTrue(false)) - throw ClassUniqueToAlwaysTrue(); -#endif // GTEST_HAS_EXCEPTIONS - return true; -} - -// If *pstr starts with the given prefix, modifies *pstr to be right -// past the prefix and returns true; otherwise leaves *pstr unchanged -// and returns false. None of pstr, *pstr, and prefix can be NULL. -bool SkipPrefix(const char* prefix, const char** pstr) { - const size_t prefix_len = strlen(prefix); - if (strncmp(*pstr, prefix, prefix_len) == 0) { - *pstr += prefix_len; - return true; - } - return false; -} - -// Parses a string as a command line flag. The string should have -// the format "--flag=value". When def_optional is true, the "=value" -// part can be omitted. -// -// Returns the value of the flag, or NULL if the parsing failed. -static const char* ParseFlagValue(const char* str, const char* flag, - bool def_optional) { - // str and flag must not be NULL. - if (str == nullptr || flag == nullptr) return nullptr; - - // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. - const std::string flag_str = std::string("--") + GTEST_FLAG_PREFIX_ + flag; - const size_t flag_len = flag_str.length(); - if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr; - - // Skips the flag name. - const char* flag_end = str + flag_len; - - // When def_optional is true, it's OK to not have a "=value" part. - if (def_optional && (flag_end[0] == '\0')) { - return flag_end; - } - - // If def_optional is true and there are more characters after the - // flag name, or if def_optional is false, there must be a '=' after - // the flag name. - if (flag_end[0] != '=') return nullptr; - - // Returns the string after "=". - return flag_end + 1; -} - -// Parses a string for a bool flag, in the form of either -// "--flag=value" or "--flag". -// -// In the former case, the value is taken as true as long as it does -// not start with '0', 'f', or 'F'. -// -// In the latter case, the value is taken as true. -// -// On success, stores the value of the flag in *value, and returns -// true. On failure, returns false without changing *value. -static bool ParseBoolFlag(const char* str, const char* flag, bool* value) { - // Gets the value of the flag as a string. - const char* const value_str = ParseFlagValue(str, flag, true); - - // Aborts if the parsing failed. - if (value_str == nullptr) return false; - - // Converts the string value to a bool. - *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); - return true; -} - -// Parses a string for an Int32 flag, in the form of -// "--flag=value". -// -// On success, stores the value of the flag in *value, and returns -// true. On failure, returns false without changing *value. -bool ParseInt32Flag(const char* str, const char* flag, Int32* value) { - // Gets the value of the flag as a string. - const char* const value_str = ParseFlagValue(str, flag, false); - - // Aborts if the parsing failed. - if (value_str == nullptr) return false; - - // Sets *value to the value of the flag. - return ParseInt32(Message() << "The value of flag --" << flag, - value_str, value); -} - -// Parses a string for a string flag, in the form of -// "--flag=value". -// -// On success, stores the value of the flag in *value, and returns -// true. On failure, returns false without changing *value. -template -static bool ParseStringFlag(const char* str, const char* flag, String* value) { - // Gets the value of the flag as a string. - const char* const value_str = ParseFlagValue(str, flag, false); - - // Aborts if the parsing failed. - if (value_str == nullptr) return false; - - // Sets *value to the value of the flag. - *value = value_str; - return true; -} - -// Determines whether a string has a prefix that Google Test uses for its -// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. -// If Google Test detects that a command line flag has its prefix but is not -// recognized, it will print its help message. Flags starting with -// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test -// internal flags and do not trigger the help message. -static bool HasGoogleTestFlagPrefix(const char* str) { - return (SkipPrefix("--", &str) || - SkipPrefix("-", &str) || - SkipPrefix("/", &str)) && - !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && - (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || - SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); -} - -// Prints a string containing code-encoded text. The following escape -// sequences can be used in the string to control the text color: -// -// @@ prints a single '@' character. -// @R changes the color to red. -// @G changes the color to green. -// @Y changes the color to yellow. -// @D changes to the default terminal text color. -// -static void PrintColorEncoded(const char* str) { - GTestColor color = COLOR_DEFAULT; // The current color. - - // Conceptually, we split the string into segments divided by escape - // sequences. Then we print one segment at a time. At the end of - // each iteration, the str pointer advances to the beginning of the - // next segment. - for (;;) { - const char* p = strchr(str, '@'); - if (p == nullptr) { - ColoredPrintf(color, "%s", str); - return; - } - - ColoredPrintf(color, "%s", std::string(str, p).c_str()); - - const char ch = p[1]; - str = p + 2; - if (ch == '@') { - ColoredPrintf(color, "@"); - } else if (ch == 'D') { - color = COLOR_DEFAULT; - } else if (ch == 'R') { - color = COLOR_RED; - } else if (ch == 'G') { - color = COLOR_GREEN; - } else if (ch == 'Y') { - color = COLOR_YELLOW; - } else { - --str; - } - } -} - -static const char kColorEncodedHelpMessage[] = -"This program contains tests written using " GTEST_NAME_ ". You can use the\n" -"following command line flags to control its behavior:\n" -"\n" -"Test Selection:\n" -" @G--" GTEST_FLAG_PREFIX_ "list_tests@D\n" -" List the names of all tests instead of running them. The name of\n" -" TEST(Foo, Bar) is \"Foo.Bar\".\n" -" @G--" GTEST_FLAG_PREFIX_ "filter=@YPOSTIVE_PATTERNS" - "[@G-@YNEGATIVE_PATTERNS]@D\n" -" Run only the tests whose name matches one of the positive patterns but\n" -" none of the negative patterns. '?' matches any single character; '*'\n" -" matches any substring; ':' separates two patterns.\n" -" @G--" GTEST_FLAG_PREFIX_ "also_run_disabled_tests@D\n" -" Run all disabled tests too.\n" -"\n" -"Test Execution:\n" -" @G--" GTEST_FLAG_PREFIX_ "repeat=@Y[COUNT]@D\n" -" Run the tests repeatedly; use a negative count to repeat forever.\n" -" @G--" GTEST_FLAG_PREFIX_ "shuffle@D\n" -" Randomize tests' orders on every iteration.\n" -" @G--" GTEST_FLAG_PREFIX_ "random_seed=@Y[NUMBER]@D\n" -" Random number seed to use for shuffling test orders (between 1 and\n" -" 99999, or 0 to use a seed based on the current time).\n" -"\n" -"Test Output:\n" -" @G--" GTEST_FLAG_PREFIX_ "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" -" Enable/disable colored output. The default is @Gauto@D.\n" -" -@G-" GTEST_FLAG_PREFIX_ "print_time=0@D\n" -" Don't print the elapsed time of each test.\n" -" @G--" GTEST_FLAG_PREFIX_ "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G" - GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" -" Generate a JSON or XML report in the given directory or with the given\n" -" file name. @YFILE_PATH@D defaults to @Gtest_detail.xml@D.\n" -# if GTEST_CAN_STREAM_RESULTS_ -" @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n" -" Stream test results to the given server.\n" -# endif // GTEST_CAN_STREAM_RESULTS_ -"\n" -"Assertion Behavior:\n" -# if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS -" @G--" GTEST_FLAG_PREFIX_ "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" -" Set the default death test style.\n" -# endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS -" @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n" -" Turn assertion failures into debugger break-points.\n" -" @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n" -" Turn assertion failures into C++ exceptions for use by an external\n" -" test framework.\n" -" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n" -" Do not report exceptions as test failures. Instead, allow them\n" -" to crash the program or throw a pop-up (on Windows).\n" -"\n" -"Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set " - "the corresponding\n" -"environment variable of a flag (all letters in upper-case). For example, to\n" -"disable colored text output, you can either specify @G--" GTEST_FLAG_PREFIX_ - "color=no@D or set\n" -"the @G" GTEST_FLAG_PREFIX_UPPER_ "COLOR@D environment variable to @Gno@D.\n" -"\n" -"For more information, please read the " GTEST_NAME_ " documentation at\n" -"@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ "\n" -"(not one in your own code or tests), please report it to\n" -"@G<" GTEST_DEV_EMAIL_ ">@D.\n"; - -static bool ParseGoogleTestFlag(const char* const arg) { - return ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, - >EST_FLAG(also_run_disabled_tests)) || - ParseBoolFlag(arg, kBreakOnFailureFlag, - >EST_FLAG(break_on_failure)) || - ParseBoolFlag(arg, kCatchExceptionsFlag, - >EST_FLAG(catch_exceptions)) || - ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || - ParseStringFlag(arg, kDeathTestStyleFlag, - >EST_FLAG(death_test_style)) || - ParseBoolFlag(arg, kDeathTestUseFork, - >EST_FLAG(death_test_use_fork)) || - ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || - ParseStringFlag(arg, kInternalRunDeathTestFlag, - >EST_FLAG(internal_run_death_test)) || - ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || - ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || - ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || - ParseBoolFlag(arg, kPrintUTF8Flag, >EST_FLAG(print_utf8)) || - ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || - ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || - ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || - ParseInt32Flag(arg, kStackTraceDepthFlag, - >EST_FLAG(stack_trace_depth)) || - ParseStringFlag(arg, kStreamResultToFlag, - >EST_FLAG(stream_result_to)) || - ParseBoolFlag(arg, kThrowOnFailureFlag, - >EST_FLAG(throw_on_failure)); -} - -#if GTEST_USE_OWN_FLAGFILE_FLAG_ -static void LoadFlagsFromFile(const std::string& path) { - FILE* flagfile = posix::FOpen(path.c_str(), "r"); - if (!flagfile) { - GTEST_LOG_(FATAL) << "Unable to open file \"" << GTEST_FLAG(flagfile) - << "\""; - } - std::string contents(ReadEntireFile(flagfile)); - posix::FClose(flagfile); - std::vector lines; - SplitString(contents, '\n', &lines); - for (size_t i = 0; i < lines.size(); ++i) { - if (lines[i].empty()) - continue; - if (!ParseGoogleTestFlag(lines[i].c_str())) - g_help_flag = true; - } -} -#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ - -// Parses the command line for Google Test flags, without initializing -// other parts of Google Test. The type parameter CharType can be -// instantiated to either char or wchar_t. -template -void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { - for (int i = 1; i < *argc; i++) { - const std::string arg_string = StreamableToString(argv[i]); - const char* const arg = arg_string.c_str(); - - using internal::ParseBoolFlag; - using internal::ParseInt32Flag; - using internal::ParseStringFlag; - - bool remove_flag = false; - if (ParseGoogleTestFlag(arg)) { - remove_flag = true; -#if GTEST_USE_OWN_FLAGFILE_FLAG_ - } else if (ParseStringFlag(arg, kFlagfileFlag, >EST_FLAG(flagfile))) { - LoadFlagsFromFile(GTEST_FLAG(flagfile)); - remove_flag = true; -#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ - } else if (arg_string == "--help" || arg_string == "-h" || - arg_string == "-?" || arg_string == "/?" || - HasGoogleTestFlagPrefix(arg)) { - // Both help flag and unrecognized Google Test flags (excluding - // internal ones) trigger help display. - g_help_flag = true; - } - - if (remove_flag) { - // Shift the remainder of the argv list left by one. Note - // that argv has (*argc + 1) elements, the last one always being - // NULL. The following loop moves the trailing NULL element as - // well. - for (int j = i; j != *argc; j++) { - argv[j] = argv[j + 1]; - } - - // Decrements the argument count. - (*argc)--; - - // We also need to decrement the iterator as we just removed - // an element. - i--; - } - } - - if (g_help_flag) { - // We print the help here instead of in RUN_ALL_TESTS(), as the - // latter may not be called at all if the user is using Google - // Test with another testing framework. - PrintColorEncoded(kColorEncodedHelpMessage); - } -} - -// Parses the command line for Google Test flags, without initializing -// other parts of Google Test. -void ParseGoogleTestFlagsOnly(int* argc, char** argv) { - ParseGoogleTestFlagsOnlyImpl(argc, argv); - - // Fix the value of *_NSGetArgc() on macOS, but iff - // *_NSGetArgv() == argv - // Only applicable to char** version of argv -#if GTEST_OS_MAC -#ifndef GTEST_OS_IOS - if (*_NSGetArgv() == argv) { - *_NSGetArgc() = *argc; - } -#endif -#endif -} -void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { - ParseGoogleTestFlagsOnlyImpl(argc, argv); -} - -// The internal implementation of InitGoogleTest(). -// -// The type parameter CharType can be instantiated to either char or -// wchar_t. -template -void InitGoogleTestImpl(int* argc, CharType** argv) { - // We don't want to run the initialization code twice. - if (GTestIsInitialized()) return; - - if (*argc <= 0) return; - - g_argvs.clear(); - for (int i = 0; i != *argc; i++) { - g_argvs.push_back(StreamableToString(argv[i])); - } - -#if GTEST_HAS_ABSL - absl::InitializeSymbolizer(g_argvs[0].c_str()); -#endif // GTEST_HAS_ABSL - - ParseGoogleTestFlagsOnly(argc, argv); - GetUnitTestImpl()->PostFlagParsingInit(); -} - -} // namespace internal - -// Initializes Google Test. This must be called before calling -// RUN_ALL_TESTS(). In particular, it parses a command line for the -// flags that Google Test recognizes. Whenever a Google Test flag is -// seen, it is removed from argv, and *argc is decremented. -// -// No value is returned. Instead, the Google Test flag variables are -// updated. -// -// Calling the function for the second time has no user-visible effect. -void InitGoogleTest(int* argc, char** argv) { -#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); -#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - internal::InitGoogleTestImpl(argc, argv); -#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) -} - -// This overloaded version can be used in Windows programs compiled in -// UNICODE mode. -void InitGoogleTest(int* argc, wchar_t** argv) { -#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv); -#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - internal::InitGoogleTestImpl(argc, argv); -#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) -} - -// This overloaded version can be used on Arduino/embedded platforms where -// there is no argc/argv. -void InitGoogleTest() { - // Since Arduino doesn't have a command line, fake out the argc/argv arguments - int argc = 1; - const auto arg0 = "dummy"; - char* argv0 = const_cast(arg0); - char** argv = &argv0; - -#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(&argc, argv); -#else // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) - internal::InitGoogleTestImpl(&argc, argv); -#endif // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_) -} - -std::string TempDir() { -#if defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_) - return GTEST_CUSTOM_TEMPDIR_FUNCTION_(); -#endif - -#if GTEST_OS_WINDOWS_MOBILE - return "\\temp\\"; -#elif GTEST_OS_WINDOWS - const char* temp_dir = internal::posix::GetEnv("TEMP"); - if (temp_dir == nullptr || temp_dir[0] == '\0') - return "\\temp\\"; - else if (temp_dir[strlen(temp_dir) - 1] == '\\') - return temp_dir; - else - return std::string(temp_dir) + "\\"; -#elif GTEST_OS_LINUX_ANDROID - return "/sdcard/"; -#else - return "/tmp/"; -#endif // GTEST_OS_WINDOWS_MOBILE -} - -// Class ScopedTrace - -// Pushes the given source file location and message onto a per-thread -// trace stack maintained by Google Test. -void ScopedTrace::PushTrace(const char* file, int line, std::string message) { - internal::TraceInfo trace; - trace.file = file; - trace.line = line; - trace.message.swap(message); - - UnitTest::GetInstance()->PushGTestTrace(trace); -} - -// Pops the info pushed by the c'tor. -ScopedTrace::~ScopedTrace() - GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) { - UnitTest::GetInstance()->PopGTestTrace(); -} - -} // namespace testing -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// This file implements death tests. - - -#include - - -#if GTEST_HAS_DEATH_TEST - -# if GTEST_OS_MAC -# include -# endif // GTEST_OS_MAC - -# include -# include -# include - -# if GTEST_OS_LINUX -# include -# endif // GTEST_OS_LINUX - -# include - -# if GTEST_OS_WINDOWS -# include -# else -# include -# include -# endif // GTEST_OS_WINDOWS - -# if GTEST_OS_QNX -# include -# endif // GTEST_OS_QNX - -# if GTEST_OS_FUCHSIA -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# endif // GTEST_OS_FUCHSIA - -#endif // GTEST_HAS_DEATH_TEST - - -namespace testing { - -// Constants. - -// The default death test style. -// -// This is defined in internal/gtest-port.h as "fast", but can be overridden by -// a definition in internal/custom/gtest-port.h. The recommended value, which is -// used internally at Google, is "threadsafe". -static const char kDefaultDeathTestStyle[] = GTEST_DEFAULT_DEATH_TEST_STYLE; - -GTEST_DEFINE_string_( - death_test_style, - internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle), - "Indicates how to run a death test in a forked child process: " - "\"threadsafe\" (child process re-executes the test binary " - "from the beginning, running only the specific death test) or " - "\"fast\" (child process runs the death test immediately " - "after forking)."); - -GTEST_DEFINE_bool_( - death_test_use_fork, - internal::BoolFromGTestEnv("death_test_use_fork", false), - "Instructs to use fork()/_exit() instead of clone() in death tests. " - "Ignored and always uses fork() on POSIX systems where clone() is not " - "implemented. Useful when running under valgrind or similar tools if " - "those do not support clone(). Valgrind 3.3.1 will just fail if " - "it sees an unsupported combination of clone() flags. " - "It is not recommended to use this flag w/o valgrind though it will " - "work in 99% of the cases. Once valgrind is fixed, this flag will " - "most likely be removed."); - -namespace internal { -GTEST_DEFINE_string_( - internal_run_death_test, "", - "Indicates the file, line number, temporal index of " - "the single death test to run, and a file descriptor to " - "which a success code may be sent, all separated by " - "the '|' characters. This flag is specified if and only if the current " - "process is a sub-process launched for running a thread-safe " - "death test. FOR INTERNAL USE ONLY."); -} // namespace internal - -#if GTEST_HAS_DEATH_TEST - -namespace internal { - -// Valid only for fast death tests. Indicates the code is running in the -// child process of a fast style death test. -# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA -static bool g_in_fast_death_test_child = false; -# endif - -// Returns a Boolean value indicating whether the caller is currently -// executing in the context of the death test child process. Tools such as -// Valgrind heap checkers may need this to modify their behavior in death -// tests. IMPORTANT: This is an internal utility. Using it may break the -// implementation of death tests. User code MUST NOT use it. -bool InDeathTestChild() { -# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA - - // On Windows and Fuchsia, death tests are thread-safe regardless of the value - // of the death_test_style flag. - return !GTEST_FLAG(internal_run_death_test).empty(); - -# else - - if (GTEST_FLAG(death_test_style) == "threadsafe") - return !GTEST_FLAG(internal_run_death_test).empty(); - else - return g_in_fast_death_test_child; -#endif -} - -} // namespace internal - -// ExitedWithCode constructor. -ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { -} - -// ExitedWithCode function-call operator. -bool ExitedWithCode::operator()(int exit_status) const { -# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA - - return exit_status == exit_code_; - -# else - - return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; - -# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA -} - -# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA -// KilledBySignal constructor. -KilledBySignal::KilledBySignal(int signum) : signum_(signum) { -} - -// KilledBySignal function-call operator. -bool KilledBySignal::operator()(int exit_status) const { -# if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) - { - bool result; - if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) { - return result; - } - } -# endif // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_) - return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; -} -# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA - -namespace internal { - -// Utilities needed for death tests. - -// Generates a textual description of a given exit code, in the format -// specified by wait(2). -static std::string ExitSummary(int exit_code) { - Message m; - -# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA - - m << "Exited with exit status " << exit_code; - -# else - - if (WIFEXITED(exit_code)) { - m << "Exited with exit status " << WEXITSTATUS(exit_code); - } else if (WIFSIGNALED(exit_code)) { - m << "Terminated by signal " << WTERMSIG(exit_code); - } -# ifdef WCOREDUMP - if (WCOREDUMP(exit_code)) { - m << " (core dumped)"; - } -# endif -# endif // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA - - return m.GetString(); -} - -// Returns true if exit_status describes a process that was terminated -// by a signal, or exited normally with a nonzero exit code. -bool ExitedUnsuccessfully(int exit_status) { - return !ExitedWithCode(0)(exit_status); -} - -# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA -// Generates a textual failure message when a death test finds more than -// one thread running, or cannot determine the number of threads, prior -// to executing the given statement. It is the responsibility of the -// caller not to pass a thread_count of 1. -static std::string DeathTestThreadWarning(size_t thread_count) { - Message msg; - msg << "Death tests use fork(), which is unsafe particularly" - << " in a threaded context. For this test, " << GTEST_NAME_ << " "; - if (thread_count == 0) { - msg << "couldn't detect the number of threads."; - } else { - msg << "detected " << thread_count << " threads."; - } - msg << " See " - "https://github.com/google/googletest/blob/master/googletest/docs/" - "advanced.md#death-tests-and-threads" - << " for more explanation and suggested solutions, especially if" - << " this is the last message you see before your test times out."; - return msg.GetString(); -} -# endif // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA - -// Flag characters for reporting a death test that did not die. -static const char kDeathTestLived = 'L'; -static const char kDeathTestReturned = 'R'; -static const char kDeathTestThrew = 'T'; -static const char kDeathTestInternalError = 'I'; - -#if GTEST_OS_FUCHSIA - -// File descriptor used for the pipe in the child process. -static const int kFuchsiaReadPipeFd = 3; - -#endif - -// An enumeration describing all of the possible ways that a death test can -// conclude. DIED means that the process died while executing the test -// code; LIVED means that process lived beyond the end of the test code; -// RETURNED means that the test statement attempted to execute a return -// statement, which is not allowed; THREW means that the test statement -// returned control by throwing an exception. IN_PROGRESS means the test -// has not yet concluded. -enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; - -// Routine for aborting the program which is safe to call from an -// exec-style death test child process, in which case the error -// message is propagated back to the parent process. Otherwise, the -// message is simply printed to stderr. In either case, the program -// then exits with status 1. -static void DeathTestAbort(const std::string& message) { - // On a POSIX system, this function may be called from a threadsafe-style - // death test child process, which operates on a very small stack. Use - // the heap for any additional non-minuscule memory requirements. - const InternalRunDeathTestFlag* const flag = - GetUnitTestImpl()->internal_run_death_test_flag(); - if (flag != nullptr) { - FILE* parent = posix::FDOpen(flag->write_fd(), "w"); - fputc(kDeathTestInternalError, parent); - fprintf(parent, "%s", message.c_str()); - fflush(parent); - _exit(1); - } else { - fprintf(stderr, "%s", message.c_str()); - fflush(stderr); - posix::Abort(); - } -} - -// A replacement for CHECK that calls DeathTestAbort if the assertion -// fails. -# define GTEST_DEATH_TEST_CHECK_(expression) \ - do { \ - if (!::testing::internal::IsTrue(expression)) { \ - DeathTestAbort( \ - ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ - + ::testing::internal::StreamableToString(__LINE__) + ": " \ - + #expression); \ - } \ - } while (::testing::internal::AlwaysFalse()) - -// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for -// evaluating any system call that fulfills two conditions: it must return -// -1 on failure, and set errno to EINTR when it is interrupted and -// should be tried again. The macro expands to a loop that repeatedly -// evaluates the expression as long as it evaluates to -1 and sets -// errno to EINTR. If the expression evaluates to -1 but errno is -// something other than EINTR, DeathTestAbort is called. -# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ - do { \ - int gtest_retval; \ - do { \ - gtest_retval = (expression); \ - } while (gtest_retval == -1 && errno == EINTR); \ - if (gtest_retval == -1) { \ - DeathTestAbort( \ - ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ - + ::testing::internal::StreamableToString(__LINE__) + ": " \ - + #expression + " != -1"); \ - } \ - } while (::testing::internal::AlwaysFalse()) - -// Returns the message describing the last system error in errno. -std::string GetLastErrnoDescription() { - return errno == 0 ? "" : posix::StrError(errno); -} - -// This is called from a death test parent process to read a failure -// message from the death test child process and log it with the FATAL -// severity. On Windows, the message is read from a pipe handle. On other -// platforms, it is read from a file descriptor. -static void FailFromInternalError(int fd) { - Message error; - char buffer[256]; - int num_read; - - do { - while ((num_read = posix::Read(fd, buffer, 255)) > 0) { - buffer[num_read] = '\0'; - error << buffer; - } - } while (num_read == -1 && errno == EINTR); - - if (num_read == 0) { - GTEST_LOG_(FATAL) << error.GetString(); - } else { - const int last_error = errno; - GTEST_LOG_(FATAL) << "Error while reading death test internal: " - << GetLastErrnoDescription() << " [" << last_error << "]"; - } -} - -// Death test constructor. Increments the running death test count -// for the current test. -DeathTest::DeathTest() { - TestInfo* const info = GetUnitTestImpl()->current_test_info(); - if (info == nullptr) { - DeathTestAbort("Cannot run a death test outside of a TEST or " - "TEST_F construct"); - } -} - -// Creates and returns a death test by dispatching to the current -// death test factory. -bool DeathTest::Create(const char* statement, - Matcher matcher, const char* file, - int line, DeathTest** test) { - return GetUnitTestImpl()->death_test_factory()->Create( - statement, std::move(matcher), file, line, test); -} - -const char* DeathTest::LastMessage() { - return last_death_test_message_.c_str(); -} - -void DeathTest::set_last_death_test_message(const std::string& message) { - last_death_test_message_ = message; -} - -std::string DeathTest::last_death_test_message_; - -// Provides cross platform implementation for some death functionality. -class DeathTestImpl : public DeathTest { - protected: - DeathTestImpl(const char* a_statement, Matcher matcher) - : statement_(a_statement), - matcher_(std::move(matcher)), - spawned_(false), - status_(-1), - outcome_(IN_PROGRESS), - read_fd_(-1), - write_fd_(-1) {} - - // read_fd_ is expected to be closed and cleared by a derived class. - ~DeathTestImpl() override { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } - - void Abort(AbortReason reason) override; - bool Passed(bool status_ok) override; - - const char* statement() const { return statement_; } - bool spawned() const { return spawned_; } - void set_spawned(bool is_spawned) { spawned_ = is_spawned; } - int status() const { return status_; } - void set_status(int a_status) { status_ = a_status; } - DeathTestOutcome outcome() const { return outcome_; } - void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } - int read_fd() const { return read_fd_; } - void set_read_fd(int fd) { read_fd_ = fd; } - int write_fd() const { return write_fd_; } - void set_write_fd(int fd) { write_fd_ = fd; } - - // Called in the parent process only. Reads the result code of the death - // test child process via a pipe, interprets it to set the outcome_ - // member, and closes read_fd_. Outputs diagnostics and terminates in - // case of unexpected codes. - void ReadAndInterpretStatusByte(); - - // Returns stderr output from the child process. - virtual std::string GetErrorLogs(); - - private: - // The textual content of the code this object is testing. This class - // doesn't own this string and should not attempt to delete it. - const char* const statement_; - // A matcher that's expected to match the stderr output by the child process. - Matcher matcher_; - // True if the death test child process has been successfully spawned. - bool spawned_; - // The exit status of the child process. - int status_; - // How the death test concluded. - DeathTestOutcome outcome_; - // Descriptor to the read end of the pipe to the child process. It is - // always -1 in the child process. The child keeps its write end of the - // pipe in write_fd_. - int read_fd_; - // Descriptor to the child's write end of the pipe to the parent process. - // It is always -1 in the parent process. The parent keeps its end of the - // pipe in read_fd_. - int write_fd_; -}; - -// Called in the parent process only. Reads the result code of the death -// test child process via a pipe, interprets it to set the outcome_ -// member, and closes read_fd_. Outputs diagnostics and terminates in -// case of unexpected codes. -void DeathTestImpl::ReadAndInterpretStatusByte() { - char flag; - int bytes_read; - - // The read() here blocks until data is available (signifying the - // failure of the death test) or until the pipe is closed (signifying - // its success), so it's okay to call this in the parent before - // the child process has exited. - do { - bytes_read = posix::Read(read_fd(), &flag, 1); - } while (bytes_read == -1 && errno == EINTR); - - if (bytes_read == 0) { - set_outcome(DIED); - } else if (bytes_read == 1) { - switch (flag) { - case kDeathTestReturned: - set_outcome(RETURNED); - break; - case kDeathTestThrew: - set_outcome(THREW); - break; - case kDeathTestLived: - set_outcome(LIVED); - break; - case kDeathTestInternalError: - FailFromInternalError(read_fd()); // Does not return. - break; - default: - GTEST_LOG_(FATAL) << "Death test child process reported " - << "unexpected status byte (" - << static_cast(flag) << ")"; - } - } else { - GTEST_LOG_(FATAL) << "Read from death test child process failed: " - << GetLastErrnoDescription(); - } - GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); - set_read_fd(-1); -} - -std::string DeathTestImpl::GetErrorLogs() { - return GetCapturedStderr(); -} - -// Signals that the death test code which should have exited, didn't. -// Should be called only in a death test child process. -// Writes a status byte to the child's status file descriptor, then -// calls _exit(1). -void DeathTestImpl::Abort(AbortReason reason) { - // The parent process considers the death test to be a failure if - // it finds any data in our pipe. So, here we write a single flag byte - // to the pipe, then exit. - const char status_ch = - reason == TEST_DID_NOT_DIE ? kDeathTestLived : - reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; - - GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); - // We are leaking the descriptor here because on some platforms (i.e., - // when built as Windows DLL), destructors of global objects will still - // run after calling _exit(). On such systems, write_fd_ will be - // indirectly closed from the destructor of UnitTestImpl, causing double - // close if it is also closed here. On debug configurations, double close - // may assert. As there are no in-process buffers to flush here, we are - // relying on the OS to close the descriptor after the process terminates - // when the destructors are not run. - _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) -} - -// Returns an indented copy of stderr output for a death test. -// This makes distinguishing death test output lines from regular log lines -// much easier. -static ::std::string FormatDeathTestOutput(const ::std::string& output) { - ::std::string ret; - for (size_t at = 0; ; ) { - const size_t line_end = output.find('\n', at); - ret += "[ DEATH ] "; - if (line_end == ::std::string::npos) { - ret += output.substr(at); - break; - } - ret += output.substr(at, line_end + 1 - at); - at = line_end + 1; - } - return ret; -} - -// Assesses the success or failure of a death test, using both private -// members which have previously been set, and one argument: -// -// Private data members: -// outcome: An enumeration describing how the death test -// concluded: DIED, LIVED, THREW, or RETURNED. The death test -// fails in the latter three cases. -// status: The exit status of the child process. On *nix, it is in the -// in the format specified by wait(2). On Windows, this is the -// value supplied to the ExitProcess() API or a numeric code -// of the exception that terminated the program. -// matcher_: A matcher that's expected to match the stderr output by the child -// process. -// -// Argument: -// status_ok: true if exit_status is acceptable in the context of -// this particular death test, which fails if it is false -// -// Returns true iff all of the above conditions are met. Otherwise, the -// first failing condition, in the order given above, is the one that is -// reported. Also sets the last death test message string. -bool DeathTestImpl::Passed(bool status_ok) { - if (!spawned()) - return false; - - const std::string error_message = GetErrorLogs(); - - bool success = false; - Message buffer; - - buffer << "Death test: " << statement() << "\n"; - switch (outcome()) { - case LIVED: - buffer << " Result: failed to die.\n" - << " Error msg:\n" << FormatDeathTestOutput(error_message); - break; - case THREW: - buffer << " Result: threw an exception.\n" - << " Error msg:\n" << FormatDeathTestOutput(error_message); - break; - case RETURNED: - buffer << " Result: illegal return in test statement.\n" - << " Error msg:\n" << FormatDeathTestOutput(error_message); - break; - case DIED: - if (status_ok) { - if (matcher_.Matches(error_message)) { - success = true; - } else { - std::ostringstream stream; - matcher_.DescribeTo(&stream); - buffer << " Result: died but not with expected error.\n" - << " Expected: " << stream.str() << "\n" - << "Actual msg:\n" - << FormatDeathTestOutput(error_message); - } - } else { - buffer << " Result: died but not with expected exit code:\n" - << " " << ExitSummary(status()) << "\n" - << "Actual msg:\n" << FormatDeathTestOutput(error_message); - } - break; - case IN_PROGRESS: - default: - GTEST_LOG_(FATAL) - << "DeathTest::Passed somehow called before conclusion of test"; - } - - DeathTest::set_last_death_test_message(buffer.GetString()); - return success; -} - -# if GTEST_OS_WINDOWS -// WindowsDeathTest implements death tests on Windows. Due to the -// specifics of starting new processes on Windows, death tests there are -// always threadsafe, and Google Test considers the -// --gtest_death_test_style=fast setting to be equivalent to -// --gtest_death_test_style=threadsafe there. -// -// A few implementation notes: Like the Linux version, the Windows -// implementation uses pipes for child-to-parent communication. But due to -// the specifics of pipes on Windows, some extra steps are required: -// -// 1. The parent creates a communication pipe and stores handles to both -// ends of it. -// 2. The parent starts the child and provides it with the information -// necessary to acquire the handle to the write end of the pipe. -// 3. The child acquires the write end of the pipe and signals the parent -// using a Windows event. -// 4. Now the parent can release the write end of the pipe on its side. If -// this is done before step 3, the object's reference count goes down to -// 0 and it is destroyed, preventing the child from acquiring it. The -// parent now has to release it, or read operations on the read end of -// the pipe will not return when the child terminates. -// 5. The parent reads child's output through the pipe (outcome code and -// any possible error messages) from the pipe, and its stderr and then -// determines whether to fail the test. -// -// Note: to distinguish Win32 API calls from the local method and function -// calls, the former are explicitly resolved in the global namespace. -// -class WindowsDeathTest : public DeathTestImpl { - public: - WindowsDeathTest(const char* a_statement, Matcher matcher, - const char* file, int line) - : DeathTestImpl(a_statement, std::move(matcher)), - file_(file), - line_(line) {} - - // All of these virtual functions are inherited from DeathTest. - virtual int Wait(); - virtual TestRole AssumeRole(); - - private: - // The name of the file in which the death test is located. - const char* const file_; - // The line number on which the death test is located. - const int line_; - // Handle to the write end of the pipe to the child process. - AutoHandle write_handle_; - // Child process handle. - AutoHandle child_handle_; - // Event the child process uses to signal the parent that it has - // acquired the handle to the write end of the pipe. After seeing this - // event the parent can release its own handles to make sure its - // ReadFile() calls return when the child terminates. - AutoHandle event_handle_; -}; - -// Waits for the child in a death test to exit, returning its exit -// status, or 0 if no child process exists. As a side effect, sets the -// outcome data member. -int WindowsDeathTest::Wait() { - if (!spawned()) - return 0; - - // Wait until the child either signals that it has acquired the write end - // of the pipe or it dies. - const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() }; - switch (::WaitForMultipleObjects(2, - wait_handles, - FALSE, // Waits for any of the handles. - INFINITE)) { - case WAIT_OBJECT_0: - case WAIT_OBJECT_0 + 1: - break; - default: - GTEST_DEATH_TEST_CHECK_(false); // Should not get here. - } - - // The child has acquired the write end of the pipe or exited. - // We release the handle on our side and continue. - write_handle_.Reset(); - event_handle_.Reset(); - - ReadAndInterpretStatusByte(); - - // Waits for the child process to exit if it haven't already. This - // returns immediately if the child has already exited, regardless of - // whether previous calls to WaitForMultipleObjects synchronized on this - // handle or not. - GTEST_DEATH_TEST_CHECK_( - WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), - INFINITE)); - DWORD status_code; - GTEST_DEATH_TEST_CHECK_( - ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); - child_handle_.Reset(); - set_status(static_cast(status_code)); - return status(); -} - -// The AssumeRole process for a Windows death test. It creates a child -// process with the same executable as the current process to run the -// death test. The child process is given the --gtest_filter and -// --gtest_internal_run_death_test flags such that it knows to run the -// current death test only. -DeathTest::TestRole WindowsDeathTest::AssumeRole() { - const UnitTestImpl* const impl = GetUnitTestImpl(); - const InternalRunDeathTestFlag* const flag = - impl->internal_run_death_test_flag(); - const TestInfo* const info = impl->current_test_info(); - const int death_test_index = info->result()->death_test_count(); - - if (flag != nullptr) { - // ParseInternalRunDeathTestFlag() has performed all the necessary - // processing. - set_write_fd(flag->write_fd()); - return EXECUTE_TEST; - } - - // WindowsDeathTest uses an anonymous pipe to communicate results of - // a death test. - SECURITY_ATTRIBUTES handles_are_inheritable = {sizeof(SECURITY_ATTRIBUTES), - nullptr, TRUE}; - HANDLE read_handle, write_handle; - GTEST_DEATH_TEST_CHECK_( - ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable, - 0) // Default buffer size. - != FALSE); - set_read_fd(::_open_osfhandle(reinterpret_cast(read_handle), - O_RDONLY)); - write_handle_.Reset(write_handle); - event_handle_.Reset(::CreateEvent( - &handles_are_inheritable, - TRUE, // The event will automatically reset to non-signaled state. - FALSE, // The initial state is non-signalled. - nullptr)); // The even is unnamed. - GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != nullptr); - const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + - kFilterFlag + "=" + info->test_suite_name() + - "." + info->name(); - const std::string internal_flag = - std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + - "=" + file_ + "|" + StreamableToString(line_) + "|" + - StreamableToString(death_test_index) + "|" + - StreamableToString(static_cast(::GetCurrentProcessId())) + - // size_t has the same width as pointers on both 32-bit and 64-bit - // Windows platforms. - // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. - "|" + StreamableToString(reinterpret_cast(write_handle)) + - "|" + StreamableToString(reinterpret_cast(event_handle_.Get())); - - char executable_path[_MAX_PATH + 1]; // NOLINT - GTEST_DEATH_TEST_CHECK_(_MAX_PATH + 1 != ::GetModuleFileNameA(nullptr, - executable_path, - _MAX_PATH)); - - std::string command_line = - std::string(::GetCommandLineA()) + " " + filter_flag + " \"" + - internal_flag + "\""; - - DeathTest::set_last_death_test_message(""); - - CaptureStderr(); - // Flush the log buffers since the log streams are shared with the child. - FlushInfoLog(); - - // The child process will share the standard handles with the parent. - STARTUPINFOA startup_info; - memset(&startup_info, 0, sizeof(STARTUPINFO)); - startup_info.dwFlags = STARTF_USESTDHANDLES; - startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); - startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); - startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); - - PROCESS_INFORMATION process_info; - GTEST_DEATH_TEST_CHECK_( - ::CreateProcessA( - executable_path, const_cast(command_line.c_str()), - nullptr, // Retuned process handle is not inheritable. - nullptr, // Retuned thread handle is not inheritable. - TRUE, // Child inherits all inheritable handles (for write_handle_). - 0x0, // Default creation flags. - nullptr, // Inherit the parent's environment. - UnitTest::GetInstance()->original_working_dir(), &startup_info, - &process_info) != FALSE); - child_handle_.Reset(process_info.hProcess); - ::CloseHandle(process_info.hThread); - set_spawned(true); - return OVERSEE_TEST; -} - -# elif GTEST_OS_FUCHSIA - -class FuchsiaDeathTest : public DeathTestImpl { - public: - FuchsiaDeathTest(const char* a_statement, Matcher matcher, - const char* file, int line) - : DeathTestImpl(a_statement, std::move(matcher)), - file_(file), - line_(line) {} - - // All of these virtual functions are inherited from DeathTest. - int Wait() override; - TestRole AssumeRole() override; - std::string GetErrorLogs() override; - - private: - // The name of the file in which the death test is located. - const char* const file_; - // The line number on which the death test is located. - const int line_; - // The stderr data captured by the child process. - std::string captured_stderr_; - - zx::process child_process_; - zx::port port_; - zx::socket stderr_socket_; -}; - -// Utility class for accumulating command-line arguments. -class Arguments { - public: - Arguments() { args_.push_back(nullptr); } - - ~Arguments() { - for (std::vector::iterator i = args_.begin(); i != args_.end(); - ++i) { - free(*i); - } - } - void AddArgument(const char* argument) { - args_.insert(args_.end() - 1, posix::StrDup(argument)); - } - - template - void AddArguments(const ::std::vector& arguments) { - for (typename ::std::vector::const_iterator i = arguments.begin(); - i != arguments.end(); - ++i) { - args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); - } - } - char* const* Argv() { - return &args_[0]; - } - - int size() { - return args_.size() - 1; - } - - private: - std::vector args_; -}; - -// Waits for the child in a death test to exit, returning its exit -// status, or 0 if no child process exists. As a side effect, sets the -// outcome data member. -int FuchsiaDeathTest::Wait() { - const int kProcessKey = 0; - const int kSocketKey = 1; - - if (!spawned()) - return 0; - - // Register to wait for the child process to terminate. - zx_status_t status_zx; - status_zx = child_process_.wait_async( - port_, kProcessKey, ZX_PROCESS_TERMINATED, ZX_WAIT_ASYNC_ONCE); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - // Register to wait for the socket to be readable or closed. - status_zx = stderr_socket_.wait_async( - port_, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, - ZX_WAIT_ASYNC_ONCE); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - - bool process_terminated = false; - bool socket_closed = false; - do { - zx_port_packet_t packet = {}; - status_zx = port_.wait(zx::time::infinite(), &packet); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - - if (packet.key == kProcessKey) { - if (ZX_PKT_IS_EXCEPTION(packet.type)) { - // Process encountered an exception. Kill it directly rather than - // letting other handlers process the event. We will get a second - // kProcessKey event when the process actually terminates. - status_zx = child_process_.kill(); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - } else { - // Process terminated. - GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); - GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED); - process_terminated = true; - } - } else if (packet.key == kSocketKey) { - GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type)); - if (packet.signal.observed & ZX_SOCKET_READABLE) { - // Read data from the socket. - constexpr size_t kBufferSize = 1024; - do { - size_t old_length = captured_stderr_.length(); - size_t bytes_read = 0; - captured_stderr_.resize(old_length + kBufferSize); - status_zx = stderr_socket_.read( - 0, &captured_stderr_.front() + old_length, kBufferSize, - &bytes_read); - captured_stderr_.resize(old_length + bytes_read); - } while (status_zx == ZX_OK); - if (status_zx == ZX_ERR_PEER_CLOSED) { - socket_closed = true; - } else { - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_ERR_SHOULD_WAIT); - status_zx = stderr_socket_.wait_async( - port_, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, - ZX_WAIT_ASYNC_ONCE); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - } - } else { - GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_SOCKET_PEER_CLOSED); - socket_closed = true; - } - } - } while (!process_terminated && !socket_closed); - - ReadAndInterpretStatusByte(); - - zx_info_process_t buffer; - status_zx = child_process_.get_info( - ZX_INFO_PROCESS, &buffer, sizeof(buffer), nullptr, nullptr); - GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK); - - GTEST_DEATH_TEST_CHECK_(buffer.exited); - set_status(buffer.return_code); - return status(); -} - -// The AssumeRole process for a Fuchsia death test. It creates a child -// process with the same executable as the current process to run the -// death test. The child process is given the --gtest_filter and -// --gtest_internal_run_death_test flags such that it knows to run the -// current death test only. -DeathTest::TestRole FuchsiaDeathTest::AssumeRole() { - const UnitTestImpl* const impl = GetUnitTestImpl(); - const InternalRunDeathTestFlag* const flag = - impl->internal_run_death_test_flag(); - const TestInfo* const info = impl->current_test_info(); - const int death_test_index = info->result()->death_test_count(); - - if (flag != nullptr) { - // ParseInternalRunDeathTestFlag() has performed all the necessary - // processing. - set_write_fd(kFuchsiaReadPipeFd); - return EXECUTE_TEST; - } - - // Flush the log buffers since the log streams are shared with the child. - FlushInfoLog(); - - // Build the child process command line. - const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + - kFilterFlag + "=" + info->test_suite_name() + - "." + info->name(); - const std::string internal_flag = - std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "=" - + file_ + "|" - + StreamableToString(line_) + "|" - + StreamableToString(death_test_index); - Arguments args; - args.AddArguments(GetInjectableArgvs()); - args.AddArgument(filter_flag.c_str()); - args.AddArgument(internal_flag.c_str()); - - // Build the pipe for communication with the child. - zx_status_t status; - zx_handle_t child_pipe_handle; - int child_pipe_fd; - status = fdio_pipe_half2(&child_pipe_fd, &child_pipe_handle); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - set_read_fd(child_pipe_fd); - - // Set the pipe handle for the child. - fdio_spawn_action_t spawn_actions[2] = {}; - fdio_spawn_action_t* add_handle_action = &spawn_actions[0]; - add_handle_action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; - add_handle_action->h.id = PA_HND(PA_FD, kFuchsiaReadPipeFd); - add_handle_action->h.handle = child_pipe_handle; - - // Create a socket pair will be used to receive the child process' stderr. - zx::socket stderr_producer_socket; - status = - zx::socket::create(0, &stderr_producer_socket, &stderr_socket_); - GTEST_DEATH_TEST_CHECK_(status >= 0); - int stderr_producer_fd = -1; - status = - fdio_fd_create(stderr_producer_socket.release(), &stderr_producer_fd); - GTEST_DEATH_TEST_CHECK_(status >= 0); - - // Make the stderr socket nonblocking. - GTEST_DEATH_TEST_CHECK_(fcntl(stderr_producer_fd, F_SETFL, 0) == 0); - - fdio_spawn_action_t* add_stderr_action = &spawn_actions[1]; - add_stderr_action->action = FDIO_SPAWN_ACTION_CLONE_FD; - add_stderr_action->fd.local_fd = stderr_producer_fd; - add_stderr_action->fd.target_fd = STDERR_FILENO; - - // Create a child job. - zx_handle_t child_job = ZX_HANDLE_INVALID; - status = zx_job_create(zx_job_default(), 0, & child_job); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - zx_policy_basic_t policy; - policy.condition = ZX_POL_NEW_ANY; - policy.policy = ZX_POL_ACTION_ALLOW; - status = zx_job_set_policy( - child_job, ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, &policy, 1); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - - // Create an exception port and attach it to the |child_job|, to allow - // us to suppress the system default exception handler from firing. - status = zx::port::create(0, &port_); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - status = zx_task_bind_exception_port( - child_job, port_.get(), 0 /* key */, 0 /*options */); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - - // Spawn the child process. - status = fdio_spawn_etc( - child_job, FDIO_SPAWN_CLONE_ALL, args.Argv()[0], args.Argv(), nullptr, - 2, spawn_actions, child_process_.reset_and_get_address(), nullptr); - GTEST_DEATH_TEST_CHECK_(status == ZX_OK); - - set_spawned(true); - return OVERSEE_TEST; -} - -std::string FuchsiaDeathTest::GetErrorLogs() { - return captured_stderr_; -} - -#else // We are neither on Windows, nor on Fuchsia. - -// ForkingDeathTest provides implementations for most of the abstract -// methods of the DeathTest interface. Only the AssumeRole method is -// left undefined. -class ForkingDeathTest : public DeathTestImpl { - public: - ForkingDeathTest(const char* statement, Matcher matcher); - - // All of these virtual functions are inherited from DeathTest. - int Wait() override; - - protected: - void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } - - private: - // PID of child process during death test; 0 in the child process itself. - pid_t child_pid_; -}; - -// Constructs a ForkingDeathTest. -ForkingDeathTest::ForkingDeathTest(const char* a_statement, - Matcher matcher) - : DeathTestImpl(a_statement, std::move(matcher)), child_pid_(-1) {} - -// Waits for the child in a death test to exit, returning its exit -// status, or 0 if no child process exists. As a side effect, sets the -// outcome data member. -int ForkingDeathTest::Wait() { - if (!spawned()) - return 0; - - ReadAndInterpretStatusByte(); - - int status_value; - GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); - set_status(status_value); - return status_value; -} - -// A concrete death test class that forks, then immediately runs the test -// in the child process. -class NoExecDeathTest : public ForkingDeathTest { - public: - NoExecDeathTest(const char* a_statement, Matcher matcher) - : ForkingDeathTest(a_statement, std::move(matcher)) {} - TestRole AssumeRole() override; -}; - -// The AssumeRole process for a fork-and-run death test. It implements a -// straightforward fork, with a simple pipe to transmit the status byte. -DeathTest::TestRole NoExecDeathTest::AssumeRole() { - const size_t thread_count = GetThreadCount(); - if (thread_count != 1) { - GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); - } - - int pipe_fd[2]; - GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); - - DeathTest::set_last_death_test_message(""); - CaptureStderr(); - // When we fork the process below, the log file buffers are copied, but the - // file descriptors are shared. We flush all log files here so that closing - // the file descriptors in the child process doesn't throw off the - // synchronization between descriptors and buffers in the parent process. - // This is as close to the fork as possible to avoid a race condition in case - // there are multiple threads running before the death test, and another - // thread writes to the log file. - FlushInfoLog(); - - const pid_t child_pid = fork(); - GTEST_DEATH_TEST_CHECK_(child_pid != -1); - set_child_pid(child_pid); - if (child_pid == 0) { - GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); - set_write_fd(pipe_fd[1]); - // Redirects all logging to stderr in the child process to prevent - // concurrent writes to the log files. We capture stderr in the parent - // process and append the child process' output to a log. - LogToStderr(); - // Event forwarding to the listeners of event listener API mush be shut - // down in death test subprocesses. - GetUnitTestImpl()->listeners()->SuppressEventForwarding(); - g_in_fast_death_test_child = true; - return EXECUTE_TEST; - } else { - GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); - set_read_fd(pipe_fd[0]); - set_spawned(true); - return OVERSEE_TEST; - } -} - -// A concrete death test class that forks and re-executes the main -// program from the beginning, with command-line flags set that cause -// only this specific death test to be run. -class ExecDeathTest : public ForkingDeathTest { - public: - ExecDeathTest(const char* a_statement, Matcher matcher, - const char* file, int line) - : ForkingDeathTest(a_statement, std::move(matcher)), - file_(file), - line_(line) {} - TestRole AssumeRole() override; - - private: - static ::std::vector GetArgvsForDeathTestChildProcess() { - ::std::vector args = GetInjectableArgvs(); -# if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) - ::std::vector extra_args = - GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_(); - args.insert(args.end(), extra_args.begin(), extra_args.end()); -# endif // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_) - return args; - } - // The name of the file in which the death test is located. - const char* const file_; - // The line number on which the death test is located. - const int line_; -}; - -// Utility class for accumulating command-line arguments. -class Arguments { - public: - Arguments() { args_.push_back(nullptr); } - - ~Arguments() { - for (std::vector::iterator i = args_.begin(); i != args_.end(); - ++i) { - free(*i); - } - } - void AddArgument(const char* argument) { - args_.insert(args_.end() - 1, posix::StrDup(argument)); - } - - template - void AddArguments(const ::std::vector& arguments) { - for (typename ::std::vector::const_iterator i = arguments.begin(); - i != arguments.end(); - ++i) { - args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); - } - } - char* const* Argv() { - return &args_[0]; - } - - private: - std::vector args_; -}; - -// A struct that encompasses the arguments to the child process of a -// threadsafe-style death test process. -struct ExecDeathTestArgs { - char* const* argv; // Command-line arguments for the child's call to exec - int close_fd; // File descriptor to close; the read end of a pipe -}; - -# if GTEST_OS_MAC -inline char** GetEnviron() { - // When Google Test is built as a framework on MacOS X, the environ variable - // is unavailable. Apple's documentation (man environ) recommends using - // _NSGetEnviron() instead. - return *_NSGetEnviron(); -} -# else -// Some POSIX platforms expect you to declare environ. extern "C" makes -// it reside in the global namespace. -extern "C" char** environ; -inline char** GetEnviron() { return environ; } -# endif // GTEST_OS_MAC - -# if !GTEST_OS_QNX -// The main function for a threadsafe-style death test child process. -// This function is called in a clone()-ed process and thus must avoid -// any potentially unsafe operations like malloc or libc functions. -static int ExecDeathTestChildMain(void* child_arg) { - ExecDeathTestArgs* const args = static_cast(child_arg); - GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); - - // We need to execute the test program in the same environment where - // it was originally invoked. Therefore we change to the original - // working directory first. - const char* const original_dir = - UnitTest::GetInstance()->original_working_dir(); - // We can safely call chdir() as it's a direct system call. - if (chdir(original_dir) != 0) { - DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + - GetLastErrnoDescription()); - return EXIT_FAILURE; - } - - // We can safely call execve() as it's a direct system call. We - // cannot use execvp() as it's a libc function and thus potentially - // unsafe. Since execve() doesn't search the PATH, the user must - // invoke the test program via a valid path that contains at least - // one path separator. - execve(args->argv[0], args->argv, GetEnviron()); - DeathTestAbort(std::string("execve(") + args->argv[0] + ", ...) in " + - original_dir + " failed: " + - GetLastErrnoDescription()); - return EXIT_FAILURE; -} -# endif // !GTEST_OS_QNX - -# if GTEST_HAS_CLONE -// Two utility routines that together determine the direction the stack -// grows. -// This could be accomplished more elegantly by a single recursive -// function, but we want to guard against the unlikely possibility of -// a smart compiler optimizing the recursion away. -// -// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining -// StackLowerThanAddress into StackGrowsDown, which then doesn't give -// correct answer. -static void StackLowerThanAddress(const void* ptr, - bool* result) GTEST_NO_INLINE_; -// HWAddressSanitizer add a random tag to the MSB of the local variable address, -// making comparison result unpredictable. -GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -static void StackLowerThanAddress(const void* ptr, bool* result) { - int dummy; - *result = (&dummy < ptr); -} - -// Make sure AddressSanitizer does not tamper with the stack here. -GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -static bool StackGrowsDown() { - int dummy; - bool result; - StackLowerThanAddress(&dummy, &result); - return result; -} -# endif // GTEST_HAS_CLONE - -// Spawns a child process with the same executable as the current process in -// a thread-safe manner and instructs it to run the death test. The -// implementation uses fork(2) + exec. On systems where clone(2) is -// available, it is used instead, being slightly more thread-safe. On QNX, -// fork supports only single-threaded environments, so this function uses -// spawn(2) there instead. The function dies with an error message if -// anything goes wrong. -static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) { - ExecDeathTestArgs args = { argv, close_fd }; - pid_t child_pid = -1; - -# if GTEST_OS_QNX - // Obtains the current directory and sets it to be closed in the child - // process. - const int cwd_fd = open(".", O_RDONLY); - GTEST_DEATH_TEST_CHECK_(cwd_fd != -1); - GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC)); - // We need to execute the test program in the same environment where - // it was originally invoked. Therefore we change to the original - // working directory first. - const char* const original_dir = - UnitTest::GetInstance()->original_working_dir(); - // We can safely call chdir() as it's a direct system call. - if (chdir(original_dir) != 0) { - DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + - GetLastErrnoDescription()); - return EXIT_FAILURE; - } - - int fd_flags; - // Set close_fd to be closed after spawn. - GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD)); - GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(close_fd, F_SETFD, - fd_flags | FD_CLOEXEC)); - struct inheritance inherit = {0}; - // spawn is a system call. - child_pid = - spawn(args.argv[0], 0, nullptr, &inherit, args.argv, GetEnviron()); - // Restores the current working directory. - GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1); - GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd)); - -# else // GTEST_OS_QNX -# if GTEST_OS_LINUX - // When a SIGPROF signal is received while fork() or clone() are executing, - // the process may hang. To avoid this, we ignore SIGPROF here and re-enable - // it after the call to fork()/clone() is complete. - struct sigaction saved_sigprof_action; - struct sigaction ignore_sigprof_action; - memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action)); - sigemptyset(&ignore_sigprof_action.sa_mask); - ignore_sigprof_action.sa_handler = SIG_IGN; - GTEST_DEATH_TEST_CHECK_SYSCALL_(sigaction( - SIGPROF, &ignore_sigprof_action, &saved_sigprof_action)); -# endif // GTEST_OS_LINUX - -# if GTEST_HAS_CLONE - const bool use_fork = GTEST_FLAG(death_test_use_fork); - - if (!use_fork) { - static const bool stack_grows_down = StackGrowsDown(); - const auto stack_size = static_cast(getpagesize()); - // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. - void* const stack = mmap(nullptr, stack_size, PROT_READ | PROT_WRITE, - MAP_ANON | MAP_PRIVATE, -1, 0); - GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); - - // Maximum stack alignment in bytes: For a downward-growing stack, this - // amount is subtracted from size of the stack space to get an address - // that is within the stack space and is aligned on all systems we care - // about. As far as I know there is no ABI with stack alignment greater - // than 64. We assume stack and stack_size already have alignment of - // kMaxStackAlignment. - const size_t kMaxStackAlignment = 64; - void* const stack_top = - static_cast(stack) + - (stack_grows_down ? stack_size - kMaxStackAlignment : 0); - GTEST_DEATH_TEST_CHECK_( - static_cast(stack_size) > kMaxStackAlignment && - reinterpret_cast(stack_top) % kMaxStackAlignment == 0); - - child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); - - GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); - } -# else - const bool use_fork = true; -# endif // GTEST_HAS_CLONE - - if (use_fork && (child_pid = fork()) == 0) { - ExecDeathTestChildMain(&args); - _exit(0); - } -# endif // GTEST_OS_QNX -# if GTEST_OS_LINUX - GTEST_DEATH_TEST_CHECK_SYSCALL_( - sigaction(SIGPROF, &saved_sigprof_action, nullptr)); -# endif // GTEST_OS_LINUX - - GTEST_DEATH_TEST_CHECK_(child_pid != -1); - return child_pid; -} - -// The AssumeRole process for a fork-and-exec death test. It re-executes the -// main program from the beginning, setting the --gtest_filter -// and --gtest_internal_run_death_test flags to cause only the current -// death test to be re-run. -DeathTest::TestRole ExecDeathTest::AssumeRole() { - const UnitTestImpl* const impl = GetUnitTestImpl(); - const InternalRunDeathTestFlag* const flag = - impl->internal_run_death_test_flag(); - const TestInfo* const info = impl->current_test_info(); - const int death_test_index = info->result()->death_test_count(); - - if (flag != nullptr) { - set_write_fd(flag->write_fd()); - return EXECUTE_TEST; - } - - int pipe_fd[2]; - GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); - // Clear the close-on-exec flag on the write end of the pipe, lest - // it be closed when the child process does an exec: - GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); - - const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ + - kFilterFlag + "=" + info->test_suite_name() + - "." + info->name(); - const std::string internal_flag = - std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "=" - + file_ + "|" + StreamableToString(line_) + "|" - + StreamableToString(death_test_index) + "|" - + StreamableToString(pipe_fd[1]); - Arguments args; - args.AddArguments(GetArgvsForDeathTestChildProcess()); - args.AddArgument(filter_flag.c_str()); - args.AddArgument(internal_flag.c_str()); - - DeathTest::set_last_death_test_message(""); - - CaptureStderr(); - // See the comment in NoExecDeathTest::AssumeRole for why the next line - // is necessary. - FlushInfoLog(); - - const pid_t child_pid = ExecDeathTestSpawnChild(args.Argv(), pipe_fd[0]); - GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); - set_child_pid(child_pid); - set_read_fd(pipe_fd[0]); - set_spawned(true); - return OVERSEE_TEST; -} - -# endif // !GTEST_OS_WINDOWS - -// Creates a concrete DeathTest-derived class that depends on the -// --gtest_death_test_style flag, and sets the pointer pointed to -// by the "test" argument to its address. If the test should be -// skipped, sets that pointer to NULL. Returns true, unless the -// flag is set to an invalid value. -bool DefaultDeathTestFactory::Create(const char* statement, - Matcher matcher, - const char* file, int line, - DeathTest** test) { - UnitTestImpl* const impl = GetUnitTestImpl(); - const InternalRunDeathTestFlag* const flag = - impl->internal_run_death_test_flag(); - const int death_test_index = impl->current_test_info() - ->increment_death_test_count(); - - if (flag != nullptr) { - if (death_test_index > flag->index()) { - DeathTest::set_last_death_test_message( - "Death test count (" + StreamableToString(death_test_index) - + ") somehow exceeded expected maximum (" - + StreamableToString(flag->index()) + ")"); - return false; - } - - if (!(flag->file() == file && flag->line() == line && - flag->index() == death_test_index)) { - *test = nullptr; - return true; - } - } - -# if GTEST_OS_WINDOWS - - if (GTEST_FLAG(death_test_style) == "threadsafe" || - GTEST_FLAG(death_test_style) == "fast") { - *test = new WindowsDeathTest(statement, std::move(matcher), file, line); - } - -# elif GTEST_OS_FUCHSIA - - if (GTEST_FLAG(death_test_style) == "threadsafe" || - GTEST_FLAG(death_test_style) == "fast") { - *test = new FuchsiaDeathTest(statement, std::move(matcher), file, line); - } - -# else - - if (GTEST_FLAG(death_test_style) == "threadsafe") { - *test = new ExecDeathTest(statement, std::move(matcher), file, line); - } else if (GTEST_FLAG(death_test_style) == "fast") { - *test = new NoExecDeathTest(statement, std::move(matcher)); - } - -# endif // GTEST_OS_WINDOWS - - else { // NOLINT - this is more readable than unbalanced brackets inside #if. - DeathTest::set_last_death_test_message( - "Unknown death test style \"" + GTEST_FLAG(death_test_style) - + "\" encountered"); - return false; - } - - return true; -} - -# if GTEST_OS_WINDOWS -// Recreates the pipe and event handles from the provided parameters, -// signals the event, and returns a file descriptor wrapped around the pipe -// handle. This function is called in the child process only. -static int GetStatusFileDescriptor(unsigned int parent_process_id, - size_t write_handle_as_size_t, - size_t event_handle_as_size_t) { - AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, - FALSE, // Non-inheritable. - parent_process_id)); - if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { - DeathTestAbort("Unable to open parent process " + - StreamableToString(parent_process_id)); - } - - GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); - - const HANDLE write_handle = - reinterpret_cast(write_handle_as_size_t); - HANDLE dup_write_handle; - - // The newly initialized handle is accessible only in the parent - // process. To obtain one accessible within the child, we need to use - // DuplicateHandle. - if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, - ::GetCurrentProcess(), &dup_write_handle, - 0x0, // Requested privileges ignored since - // DUPLICATE_SAME_ACCESS is used. - FALSE, // Request non-inheritable handler. - DUPLICATE_SAME_ACCESS)) { - DeathTestAbort("Unable to duplicate the pipe handle " + - StreamableToString(write_handle_as_size_t) + - " from the parent process " + - StreamableToString(parent_process_id)); - } - - const HANDLE event_handle = reinterpret_cast(event_handle_as_size_t); - HANDLE dup_event_handle; - - if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, - ::GetCurrentProcess(), &dup_event_handle, - 0x0, - FALSE, - DUPLICATE_SAME_ACCESS)) { - DeathTestAbort("Unable to duplicate the event handle " + - StreamableToString(event_handle_as_size_t) + - " from the parent process " + - StreamableToString(parent_process_id)); - } - - const int write_fd = - ::_open_osfhandle(reinterpret_cast(dup_write_handle), O_APPEND); - if (write_fd == -1) { - DeathTestAbort("Unable to convert pipe handle " + - StreamableToString(write_handle_as_size_t) + - " to a file descriptor"); - } - - // Signals the parent that the write end of the pipe has been acquired - // so the parent can release its own write end. - ::SetEvent(dup_event_handle); - - return write_fd; -} -# endif // GTEST_OS_WINDOWS - -// Returns a newly created InternalRunDeathTestFlag object with fields -// initialized from the GTEST_FLAG(internal_run_death_test) flag if -// the flag is specified; otherwise returns NULL. -InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { - if (GTEST_FLAG(internal_run_death_test) == "") return nullptr; - - // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we - // can use it here. - int line = -1; - int index = -1; - ::std::vector< ::std::string> fields; - SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); - int write_fd = -1; - -# if GTEST_OS_WINDOWS - - unsigned int parent_process_id = 0; - size_t write_handle_as_size_t = 0; - size_t event_handle_as_size_t = 0; - - if (fields.size() != 6 - || !ParseNaturalNumber(fields[1], &line) - || !ParseNaturalNumber(fields[2], &index) - || !ParseNaturalNumber(fields[3], &parent_process_id) - || !ParseNaturalNumber(fields[4], &write_handle_as_size_t) - || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { - DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + - GTEST_FLAG(internal_run_death_test)); - } - write_fd = GetStatusFileDescriptor(parent_process_id, - write_handle_as_size_t, - event_handle_as_size_t); - -# elif GTEST_OS_FUCHSIA - - if (fields.size() != 3 - || !ParseNaturalNumber(fields[1], &line) - || !ParseNaturalNumber(fields[2], &index)) { - DeathTestAbort("Bad --gtest_internal_run_death_test flag: " - + GTEST_FLAG(internal_run_death_test)); - } - -# else - - if (fields.size() != 4 - || !ParseNaturalNumber(fields[1], &line) - || !ParseNaturalNumber(fields[2], &index) - || !ParseNaturalNumber(fields[3], &write_fd)) { - DeathTestAbort("Bad --gtest_internal_run_death_test flag: " - + GTEST_FLAG(internal_run_death_test)); - } - -# endif // GTEST_OS_WINDOWS - - return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); -} - -} // namespace internal - -#endif // GTEST_HAS_DEATH_TEST - -} // namespace testing -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -#include - -#if GTEST_OS_WINDOWS_MOBILE -# include -#elif GTEST_OS_WINDOWS -# include -# include -#else -# include -# include // Some Linux distributions define PATH_MAX here. -#endif // GTEST_OS_WINDOWS_MOBILE - - -#if GTEST_OS_WINDOWS -# define GTEST_PATH_MAX_ _MAX_PATH -#elif defined(PATH_MAX) -# define GTEST_PATH_MAX_ PATH_MAX -#elif defined(_XOPEN_PATH_MAX) -# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX -#else -# define GTEST_PATH_MAX_ _POSIX_PATH_MAX -#endif // GTEST_OS_WINDOWS - -namespace testing { -namespace internal { - -#if GTEST_OS_WINDOWS -// On Windows, '\\' is the standard path separator, but many tools and the -// Windows API also accept '/' as an alternate path separator. Unless otherwise -// noted, a file path can contain either kind of path separators, or a mixture -// of them. -const char kPathSeparator = '\\'; -const char kAlternatePathSeparator = '/'; -const char kAlternatePathSeparatorString[] = "/"; -# if GTEST_OS_WINDOWS_MOBILE -// Windows CE doesn't have a current directory. You should not use -// the current directory in tests on Windows CE, but this at least -// provides a reasonable fallback. -const char kCurrentDirectoryString[] = "\\"; -// Windows CE doesn't define INVALID_FILE_ATTRIBUTES -const DWORD kInvalidFileAttributes = 0xffffffff; -# else -const char kCurrentDirectoryString[] = ".\\"; -# endif // GTEST_OS_WINDOWS_MOBILE -#else -const char kPathSeparator = '/'; -const char kCurrentDirectoryString[] = "./"; -#endif // GTEST_OS_WINDOWS - -// Returns whether the given character is a valid path separator. -static bool IsPathSeparator(char c) { -#if GTEST_HAS_ALT_PATH_SEP_ - return (c == kPathSeparator) || (c == kAlternatePathSeparator); -#else - return c == kPathSeparator; -#endif -} - -// Returns the current working directory, or "" if unsuccessful. -FilePath FilePath::GetCurrentDir() { -#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \ - GTEST_OS_WINDOWS_RT || ARDUINO - // Windows CE and Arduino don't have a current directory, so we just return - // something reasonable. - return FilePath(kCurrentDirectoryString); -#elif GTEST_OS_WINDOWS - char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; - return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd); -#else - char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; - char* result = getcwd(cwd, sizeof(cwd)); -# if GTEST_OS_NACL - // getcwd will likely fail in NaCl due to the sandbox, so return something - // reasonable. The user may have provided a shim implementation for getcwd, - // however, so fallback only when failure is detected. - return FilePath(result == nullptr ? kCurrentDirectoryString : cwd); -# endif // GTEST_OS_NACL - return FilePath(result == nullptr ? "" : cwd); -#endif // GTEST_OS_WINDOWS_MOBILE -} - -// Returns a copy of the FilePath with the case-insensitive extension removed. -// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns -// FilePath("dir/file"). If a case-insensitive extension is not -// found, returns a copy of the original FilePath. -FilePath FilePath::RemoveExtension(const char* extension) const { - const std::string dot_extension = std::string(".") + extension; - if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) { - return FilePath(pathname_.substr( - 0, pathname_.length() - dot_extension.length())); - } - return *this; -} - -// Returns a pointer to the last occurrence of a valid path separator in -// the FilePath. On Windows, for example, both '/' and '\' are valid path -// separators. Returns NULL if no path separator was found. -const char* FilePath::FindLastPathSeparator() const { - const char* const last_sep = strrchr(c_str(), kPathSeparator); -#if GTEST_HAS_ALT_PATH_SEP_ - const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); - // Comparing two pointers of which only one is NULL is undefined. - if (last_alt_sep != nullptr && - (last_sep == nullptr || last_alt_sep > last_sep)) { - return last_alt_sep; - } -#endif - return last_sep; -} - -// Returns a copy of the FilePath with the directory part removed. -// Example: FilePath("path/to/file").RemoveDirectoryName() returns -// FilePath("file"). If there is no directory part ("just_a_file"), it returns -// the FilePath unmodified. If there is no file part ("just_a_dir/") it -// returns an empty FilePath (""). -// On Windows platform, '\' is the path separator, otherwise it is '/'. -FilePath FilePath::RemoveDirectoryName() const { - const char* const last_sep = FindLastPathSeparator(); - return last_sep ? FilePath(last_sep + 1) : *this; -} - -// RemoveFileName returns the directory path with the filename removed. -// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". -// If the FilePath is "a_file" or "/a_file", RemoveFileName returns -// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does -// not have a file, like "just/a/dir/", it returns the FilePath unmodified. -// On Windows platform, '\' is the path separator, otherwise it is '/'. -FilePath FilePath::RemoveFileName() const { - const char* const last_sep = FindLastPathSeparator(); - std::string dir; - if (last_sep) { - dir = std::string(c_str(), static_cast(last_sep + 1 - c_str())); - } else { - dir = kCurrentDirectoryString; - } - return FilePath(dir); -} - -// Helper functions for naming files in a directory for xml output. - -// Given directory = "dir", base_name = "test", number = 0, -// extension = "xml", returns "dir/test.xml". If number is greater -// than zero (e.g., 12), returns "dir/test_12.xml". -// On Windows platform, uses \ as the separator rather than /. -FilePath FilePath::MakeFileName(const FilePath& directory, - const FilePath& base_name, - int number, - const char* extension) { - std::string file; - if (number == 0) { - file = base_name.string() + "." + extension; - } else { - file = base_name.string() + "_" + StreamableToString(number) - + "." + extension; - } - return ConcatPaths(directory, FilePath(file)); -} - -// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". -// On Windows, uses \ as the separator rather than /. -FilePath FilePath::ConcatPaths(const FilePath& directory, - const FilePath& relative_path) { - if (directory.IsEmpty()) - return relative_path; - const FilePath dir(directory.RemoveTrailingPathSeparator()); - return FilePath(dir.string() + kPathSeparator + relative_path.string()); -} - -// Returns true if pathname describes something findable in the file-system, -// either a file, directory, or whatever. -bool FilePath::FileOrDirectoryExists() const { -#if GTEST_OS_WINDOWS_MOBILE - LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); - const DWORD attributes = GetFileAttributes(unicode); - delete [] unicode; - return attributes != kInvalidFileAttributes; -#else - posix::StatStruct file_stat; - return posix::Stat(pathname_.c_str(), &file_stat) == 0; -#endif // GTEST_OS_WINDOWS_MOBILE -} - -// Returns true if pathname describes a directory in the file-system -// that exists. -bool FilePath::DirectoryExists() const { - bool result = false; -#if GTEST_OS_WINDOWS - // Don't strip off trailing separator if path is a root directory on - // Windows (like "C:\\"). - const FilePath& path(IsRootDirectory() ? *this : - RemoveTrailingPathSeparator()); -#else - const FilePath& path(*this); -#endif - -#if GTEST_OS_WINDOWS_MOBILE - LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); - const DWORD attributes = GetFileAttributes(unicode); - delete [] unicode; - if ((attributes != kInvalidFileAttributes) && - (attributes & FILE_ATTRIBUTE_DIRECTORY)) { - result = true; - } -#else - posix::StatStruct file_stat; - result = posix::Stat(path.c_str(), &file_stat) == 0 && - posix::IsDir(file_stat); -#endif // GTEST_OS_WINDOWS_MOBILE - - return result; -} - -// Returns true if pathname describes a root directory. (Windows has one -// root directory per disk drive.) -bool FilePath::IsRootDirectory() const { -#if GTEST_OS_WINDOWS - return pathname_.length() == 3 && IsAbsolutePath(); -#else - return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); -#endif -} - -// Returns true if pathname describes an absolute path. -bool FilePath::IsAbsolutePath() const { - const char* const name = pathname_.c_str(); -#if GTEST_OS_WINDOWS - return pathname_.length() >= 3 && - ((name[0] >= 'a' && name[0] <= 'z') || - (name[0] >= 'A' && name[0] <= 'Z')) && - name[1] == ':' && - IsPathSeparator(name[2]); -#else - return IsPathSeparator(name[0]); -#endif -} - -// Returns a pathname for a file that does not currently exist. The pathname -// will be directory/base_name.extension or -// directory/base_name_.extension if directory/base_name.extension -// already exists. The number will be incremented until a pathname is found -// that does not already exist. -// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. -// There could be a race condition if two or more processes are calling this -// function at the same time -- they could both pick the same filename. -FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, - const FilePath& base_name, - const char* extension) { - FilePath full_pathname; - int number = 0; - do { - full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); - } while (full_pathname.FileOrDirectoryExists()); - return full_pathname; -} - -// Returns true if FilePath ends with a path separator, which indicates that -// it is intended to represent a directory. Returns false otherwise. -// This does NOT check that a directory (or file) actually exists. -bool FilePath::IsDirectory() const { - return !pathname_.empty() && - IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); -} - -// Create directories so that path exists. Returns true if successful or if -// the directories already exist; returns false if unable to create directories -// for any reason. -bool FilePath::CreateDirectoriesRecursively() const { - if (!this->IsDirectory()) { - return false; - } - - if (pathname_.length() == 0 || this->DirectoryExists()) { - return true; - } - - const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); - return parent.CreateDirectoriesRecursively() && this->CreateFolder(); -} - -// Create the directory so that path exists. Returns true if successful or -// if the directory already exists; returns false if unable to create the -// directory for any reason, including if the parent directory does not -// exist. Not named "CreateDirectory" because that's a macro on Windows. -bool FilePath::CreateFolder() const { -#if GTEST_OS_WINDOWS_MOBILE - FilePath removed_sep(this->RemoveTrailingPathSeparator()); - LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); - int result = CreateDirectory(unicode, nullptr) ? 0 : -1; - delete [] unicode; -#elif GTEST_OS_WINDOWS - int result = _mkdir(pathname_.c_str()); -#else - int result = mkdir(pathname_.c_str(), 0777); -#endif // GTEST_OS_WINDOWS_MOBILE - - if (result == -1) { - return this->DirectoryExists(); // An error is OK if the directory exists. - } - return true; // No error. -} - -// If input name has a trailing separator character, remove it and return the -// name, otherwise return the name string unmodified. -// On Windows platform, uses \ as the separator, other platforms use /. -FilePath FilePath::RemoveTrailingPathSeparator() const { - return IsDirectory() - ? FilePath(pathname_.substr(0, pathname_.length() - 1)) - : *this; -} - -// Removes any redundant separators that might be in the pathname. -// For example, "bar///foo" becomes "bar/foo". Does not eliminate other -// redundancies that might be in a pathname involving "." or "..". -void FilePath::Normalize() { - if (pathname_.c_str() == nullptr) { - pathname_ = ""; - return; - } - const char* src = pathname_.c_str(); - char* const dest = new char[pathname_.length() + 1]; - char* dest_ptr = dest; - memset(dest_ptr, 0, pathname_.length() + 1); - - while (*src != '\0') { - *dest_ptr = *src; - if (!IsPathSeparator(*src)) { - src++; - } else { -#if GTEST_HAS_ALT_PATH_SEP_ - if (*dest_ptr == kAlternatePathSeparator) { - *dest_ptr = kPathSeparator; - } -#endif - while (IsPathSeparator(*src)) - src++; - } - dest_ptr++; - } - *dest_ptr = '\0'; - pathname_ = dest; - delete[] dest; -} - -} // namespace internal -} // namespace testing -// Copyright 2007, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This file implements just enough of the matcher interface to allow -// EXPECT_DEATH and friends to accept a matcher argument. - - -#include - -namespace testing { - -// Constructs a matcher that matches a const std::string& whose value is -// equal to s. -Matcher::Matcher(const std::string& s) { *this = Eq(s); } - -// Constructs a matcher that matches a const std::string& whose value is -// equal to s. -Matcher::Matcher(const char* s) { - *this = Eq(std::string(s)); -} - -// Constructs a matcher that matches a std::string whose value is equal to -// s. -Matcher::Matcher(const std::string& s) { *this = Eq(s); } - -// Constructs a matcher that matches a std::string whose value is equal to -// s. -Matcher::Matcher(const char* s) { *this = Eq(std::string(s)); } - -#if GTEST_HAS_ABSL -// Constructs a matcher that matches a const absl::string_view& whose value is -// equal to s. -Matcher::Matcher(const std::string& s) { - *this = Eq(s); -} - -// Constructs a matcher that matches a const absl::string_view& whose value is -// equal to s. -Matcher::Matcher(const char* s) { - *this = Eq(std::string(s)); -} - -// Constructs a matcher that matches a const absl::string_view& whose value is -// equal to s. -Matcher::Matcher(absl::string_view s) { - *this = Eq(std::string(s)); -} - -// Constructs a matcher that matches a absl::string_view whose value is equal to -// s. -Matcher::Matcher(const std::string& s) { *this = Eq(s); } - -// Constructs a matcher that matches a absl::string_view whose value is equal to -// s. -Matcher::Matcher(const char* s) { - *this = Eq(std::string(s)); -} - -// Constructs a matcher that matches a absl::string_view whose value is equal to -// s. -Matcher::Matcher(absl::string_view s) { - *this = Eq(std::string(s)); -} -#endif // GTEST_HAS_ABSL - -} // namespace testing -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -#include -#include -#include -#include -#include -#include - -#if GTEST_OS_WINDOWS -# include -# include -# include -# include // Used in ThreadLocal. -# ifdef _MSC_VER -# include -# endif // _MSC_VER -#else -# include -#endif // GTEST_OS_WINDOWS - -#if GTEST_OS_MAC -# include -# include -# include -#endif // GTEST_OS_MAC - -#if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ - GTEST_OS_NETBSD || GTEST_OS_OPENBSD -# include -# if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD -# include -# endif -#endif - -#if GTEST_OS_QNX -# include -# include -# include -#endif // GTEST_OS_QNX - -#if GTEST_OS_AIX -# include -# include -#endif // GTEST_OS_AIX - -#if GTEST_OS_FUCHSIA -# include -# include -#endif // GTEST_OS_FUCHSIA - - -namespace testing { -namespace internal { - -#if defined(_MSC_VER) || defined(__BORLANDC__) -// MSVC and C++Builder do not provide a definition of STDERR_FILENO. -const int kStdOutFileno = 1; -const int kStdErrFileno = 2; -#else -const int kStdOutFileno = STDOUT_FILENO; -const int kStdErrFileno = STDERR_FILENO; -#endif // _MSC_VER - -#if GTEST_OS_LINUX - -namespace { -template -T ReadProcFileField(const std::string& filename, int field) { - std::string dummy; - std::ifstream file(filename.c_str()); - while (field-- > 0) { - file >> dummy; - } - T output = 0; - file >> output; - return output; -} -} // namespace - -// Returns the number of active threads, or 0 when there is an error. -size_t GetThreadCount() { - const std::string filename = - (Message() << "/proc/" << getpid() << "/stat").GetString(); - return ReadProcFileField(filename, 19); -} - -#elif GTEST_OS_MAC - -size_t GetThreadCount() { - const task_t task = mach_task_self(); - mach_msg_type_number_t thread_count; - thread_act_array_t thread_list; - const kern_return_t status = task_threads(task, &thread_list, &thread_count); - if (status == KERN_SUCCESS) { - // task_threads allocates resources in thread_list and we need to free them - // to avoid leaks. - vm_deallocate(task, - reinterpret_cast(thread_list), - sizeof(thread_t) * thread_count); - return static_cast(thread_count); - } else { - return 0; - } -} - -#elif GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \ - GTEST_OS_NETBSD - -#if GTEST_OS_NETBSD -#undef KERN_PROC -#define KERN_PROC KERN_PROC2 -#define kinfo_proc kinfo_proc2 -#endif - -#if GTEST_OS_DRAGONFLY -#define KP_NLWP(kp) (kp.kp_nthreads) -#elif GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD -#define KP_NLWP(kp) (kp.ki_numthreads) -#elif GTEST_OS_NETBSD -#define KP_NLWP(kp) (kp.p_nlwps) -#endif - -// Returns the number of threads running in the process, or 0 to indicate that -// we cannot detect it. -size_t GetThreadCount() { - int mib[] = { - CTL_KERN, - KERN_PROC, - KERN_PROC_PID, - getpid(), -#if GTEST_OS_NETBSD - sizeof(struct kinfo_proc), - 1, -#endif - }; - u_int miblen = sizeof(mib) / sizeof(mib[0]); - struct kinfo_proc info; - size_t size = sizeof(info); - if (sysctl(mib, miblen, &info, &size, NULL, 0)) { - return 0; - } - return static_cast(KP_NLWP(info)); -} -#elif GTEST_OS_OPENBSD - -// Returns the number of threads running in the process, or 0 to indicate that -// we cannot detect it. -size_t GetThreadCount() { - int mib[] = { - CTL_KERN, - KERN_PROC, - KERN_PROC_PID | KERN_PROC_SHOW_THREADS, - getpid(), - sizeof(struct kinfo_proc), - 0, - }; - u_int miblen = sizeof(mib) / sizeof(mib[0]); - - // get number of structs - size_t size; - if (sysctl(mib, miblen, NULL, &size, NULL, 0)) { - return 0; - } - mib[5] = size / mib[4]; - - // populate array of structs - struct kinfo_proc info[mib[5]]; - if (sysctl(mib, miblen, &info, &size, NULL, 0)) { - return 0; - } - - // exclude empty members - int nthreads = 0; - for (int i = 0; i < size / mib[4]; i++) { - if (info[i].p_tid != -1) - nthreads++; - } - return nthreads; -} - -#elif GTEST_OS_QNX - -// Returns the number of threads running in the process, or 0 to indicate that -// we cannot detect it. -size_t GetThreadCount() { - const int fd = open("/proc/self/as", O_RDONLY); - if (fd < 0) { - return 0; - } - procfs_info process_info; - const int status = - devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), nullptr); - close(fd); - if (status == EOK) { - return static_cast(process_info.num_threads); - } else { - return 0; - } -} - -#elif GTEST_OS_AIX - -size_t GetThreadCount() { - struct procentry64 entry; - pid_t pid = getpid(); - int status = getprocs64(&entry, sizeof(entry), nullptr, 0, &pid, 1); - if (status == 1) { - return entry.pi_thcount; - } else { - return 0; - } -} - -#elif GTEST_OS_FUCHSIA - -size_t GetThreadCount() { - int dummy_buffer; - size_t avail; - zx_status_t status = zx_object_get_info( - zx_process_self(), - ZX_INFO_PROCESS_THREADS, - &dummy_buffer, - 0, - nullptr, - &avail); - if (status == ZX_OK) { - return avail; - } else { - return 0; - } -} - -#else - -size_t GetThreadCount() { - // There's no portable way to detect the number of threads, so we just - // return 0 to indicate that we cannot detect it. - return 0; -} - -#endif // GTEST_OS_LINUX - -#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS - -void SleepMilliseconds(int n) { - ::Sleep(static_cast(n)); -} - -AutoHandle::AutoHandle() - : handle_(INVALID_HANDLE_VALUE) {} - -AutoHandle::AutoHandle(Handle handle) - : handle_(handle) {} - -AutoHandle::~AutoHandle() { - Reset(); -} - -AutoHandle::Handle AutoHandle::Get() const { - return handle_; -} - -void AutoHandle::Reset() { - Reset(INVALID_HANDLE_VALUE); -} - -void AutoHandle::Reset(HANDLE handle) { - // Resetting with the same handle we already own is invalid. - if (handle_ != handle) { - if (IsCloseable()) { - ::CloseHandle(handle_); - } - handle_ = handle; - } else { - GTEST_CHECK_(!IsCloseable()) - << "Resetting a valid handle to itself is likely a programmer error " - "and thus not allowed."; - } -} - -bool AutoHandle::IsCloseable() const { - // Different Windows APIs may use either of these values to represent an - // invalid handle. - return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE; -} - -Notification::Notification() - : event_(::CreateEvent(nullptr, // Default security attributes. - TRUE, // Do not reset automatically. - FALSE, // Initially unset. - nullptr)) { // Anonymous event. - GTEST_CHECK_(event_.Get() != nullptr); -} - -void Notification::Notify() { - GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE); -} - -void Notification::WaitForNotification() { - GTEST_CHECK_( - ::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0); -} - -Mutex::Mutex() - : owner_thread_id_(0), - type_(kDynamic), - critical_section_init_phase_(0), - critical_section_(new CRITICAL_SECTION) { - ::InitializeCriticalSection(critical_section_); -} - -Mutex::~Mutex() { - // Static mutexes are leaked intentionally. It is not thread-safe to try - // to clean them up. - if (type_ == kDynamic) { - ::DeleteCriticalSection(critical_section_); - delete critical_section_; - critical_section_ = nullptr; - } -} - -void Mutex::Lock() { - ThreadSafeLazyInit(); - ::EnterCriticalSection(critical_section_); - owner_thread_id_ = ::GetCurrentThreadId(); -} - -void Mutex::Unlock() { - ThreadSafeLazyInit(); - // We don't protect writing to owner_thread_id_ here, as it's the - // caller's responsibility to ensure that the current thread holds the - // mutex when this is called. - owner_thread_id_ = 0; - ::LeaveCriticalSection(critical_section_); -} - -// Does nothing if the current thread holds the mutex. Otherwise, crashes -// with high probability. -void Mutex::AssertHeld() { - ThreadSafeLazyInit(); - GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId()) - << "The current thread is not holding the mutex @" << this; -} - -namespace { - -#ifdef _MSC_VER -// Use the RAII idiom to flag mem allocs that are intentionally never -// deallocated. The motivation is to silence the false positive mem leaks -// that are reported by the debug version of MS's CRT which can only detect -// if an alloc is missing a matching deallocation. -// Example: -// MemoryIsNotDeallocated memory_is_not_deallocated; -// critical_section_ = new CRITICAL_SECTION; -// -class MemoryIsNotDeallocated -{ - public: - MemoryIsNotDeallocated() : old_crtdbg_flag_(0) { - old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT - // doesn't report mem leak if there's no matching deallocation. - _CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF); - } - - ~MemoryIsNotDeallocated() { - // Restore the original _CRTDBG_ALLOC_MEM_DF flag - _CrtSetDbgFlag(old_crtdbg_flag_); - } - - private: - int old_crtdbg_flag_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated); -}; -#endif // _MSC_VER - -} // namespace - -// Initializes owner_thread_id_ and critical_section_ in static mutexes. -void Mutex::ThreadSafeLazyInit() { - // Dynamic mutexes are initialized in the constructor. - if (type_ == kStatic) { - switch ( - ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) { - case 0: - // If critical_section_init_phase_ was 0 before the exchange, we - // are the first to test it and need to perform the initialization. - owner_thread_id_ = 0; - { - // Use RAII to flag that following mem alloc is never deallocated. -#ifdef _MSC_VER - MemoryIsNotDeallocated memory_is_not_deallocated; -#endif // _MSC_VER - critical_section_ = new CRITICAL_SECTION; - } - ::InitializeCriticalSection(critical_section_); - // Updates the critical_section_init_phase_ to 2 to signal - // initialization complete. - GTEST_CHECK_(::InterlockedCompareExchange( - &critical_section_init_phase_, 2L, 1L) == - 1L); - break; - case 1: - // Somebody else is already initializing the mutex; spin until they - // are done. - while (::InterlockedCompareExchange(&critical_section_init_phase_, - 2L, - 2L) != 2L) { - // Possibly yields the rest of the thread's time slice to other - // threads. - ::Sleep(0); - } - break; - - case 2: - break; // The mutex is already initialized and ready for use. - - default: - GTEST_CHECK_(false) - << "Unexpected value of critical_section_init_phase_ " - << "while initializing a static mutex."; - } - } -} - -namespace { - -class ThreadWithParamSupport : public ThreadWithParamBase { - public: - static HANDLE CreateThread(Runnable* runnable, - Notification* thread_can_start) { - ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start); - DWORD thread_id; - HANDLE thread_handle = ::CreateThread( - nullptr, // Default security. - 0, // Default stack size. - &ThreadWithParamSupport::ThreadMain, - param, // Parameter to ThreadMainStatic - 0x0, // Default creation flags. - &thread_id); // Need a valid pointer for the call to work under Win98. - GTEST_CHECK_(thread_handle != nullptr) - << "CreateThread failed with error " << ::GetLastError() << "."; - if (thread_handle == nullptr) { - delete param; - } - return thread_handle; - } - - private: - struct ThreadMainParam { - ThreadMainParam(Runnable* runnable, Notification* thread_can_start) - : runnable_(runnable), - thread_can_start_(thread_can_start) { - } - std::unique_ptr runnable_; - // Does not own. - Notification* thread_can_start_; - }; - - static DWORD WINAPI ThreadMain(void* ptr) { - // Transfers ownership. - std::unique_ptr param(static_cast(ptr)); - if (param->thread_can_start_ != nullptr) - param->thread_can_start_->WaitForNotification(); - param->runnable_->Run(); - return 0; - } - - // Prohibit instantiation. - ThreadWithParamSupport(); - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport); -}; - -} // namespace - -ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable, - Notification* thread_can_start) - : thread_(ThreadWithParamSupport::CreateThread(runnable, - thread_can_start)) { -} - -ThreadWithParamBase::~ThreadWithParamBase() { - Join(); -} - -void ThreadWithParamBase::Join() { - GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0) - << "Failed to join the thread with error " << ::GetLastError() << "."; -} - -// Maps a thread to a set of ThreadIdToThreadLocals that have values -// instantiated on that thread and notifies them when the thread exits. A -// ThreadLocal instance is expected to persist until all threads it has -// values on have terminated. -class ThreadLocalRegistryImpl { - public: - // Registers thread_local_instance as having value on the current thread. - // Returns a value that can be used to identify the thread from other threads. - static ThreadLocalValueHolderBase* GetValueOnCurrentThread( - const ThreadLocalBase* thread_local_instance) { - DWORD current_thread = ::GetCurrentThreadId(); - MutexLock lock(&mutex_); - ThreadIdToThreadLocals* const thread_to_thread_locals = - GetThreadLocalsMapLocked(); - ThreadIdToThreadLocals::iterator thread_local_pos = - thread_to_thread_locals->find(current_thread); - if (thread_local_pos == thread_to_thread_locals->end()) { - thread_local_pos = thread_to_thread_locals->insert( - std::make_pair(current_thread, ThreadLocalValues())).first; - StartWatcherThreadFor(current_thread); - } - ThreadLocalValues& thread_local_values = thread_local_pos->second; - ThreadLocalValues::iterator value_pos = - thread_local_values.find(thread_local_instance); - if (value_pos == thread_local_values.end()) { - value_pos = - thread_local_values - .insert(std::make_pair( - thread_local_instance, - std::shared_ptr( - thread_local_instance->NewValueForCurrentThread()))) - .first; - } - return value_pos->second.get(); - } - - static void OnThreadLocalDestroyed( - const ThreadLocalBase* thread_local_instance) { - std::vector > value_holders; - // Clean up the ThreadLocalValues data structure while holding the lock, but - // defer the destruction of the ThreadLocalValueHolderBases. - { - MutexLock lock(&mutex_); - ThreadIdToThreadLocals* const thread_to_thread_locals = - GetThreadLocalsMapLocked(); - for (ThreadIdToThreadLocals::iterator it = - thread_to_thread_locals->begin(); - it != thread_to_thread_locals->end(); - ++it) { - ThreadLocalValues& thread_local_values = it->second; - ThreadLocalValues::iterator value_pos = - thread_local_values.find(thread_local_instance); - if (value_pos != thread_local_values.end()) { - value_holders.push_back(value_pos->second); - thread_local_values.erase(value_pos); - // This 'if' can only be successful at most once, so theoretically we - // could break out of the loop here, but we don't bother doing so. - } - } - } - // Outside the lock, let the destructor for 'value_holders' deallocate the - // ThreadLocalValueHolderBases. - } - - static void OnThreadExit(DWORD thread_id) { - GTEST_CHECK_(thread_id != 0) << ::GetLastError(); - std::vector > value_holders; - // Clean up the ThreadIdToThreadLocals data structure while holding the - // lock, but defer the destruction of the ThreadLocalValueHolderBases. - { - MutexLock lock(&mutex_); - ThreadIdToThreadLocals* const thread_to_thread_locals = - GetThreadLocalsMapLocked(); - ThreadIdToThreadLocals::iterator thread_local_pos = - thread_to_thread_locals->find(thread_id); - if (thread_local_pos != thread_to_thread_locals->end()) { - ThreadLocalValues& thread_local_values = thread_local_pos->second; - for (ThreadLocalValues::iterator value_pos = - thread_local_values.begin(); - value_pos != thread_local_values.end(); - ++value_pos) { - value_holders.push_back(value_pos->second); - } - thread_to_thread_locals->erase(thread_local_pos); - } - } - // Outside the lock, let the destructor for 'value_holders' deallocate the - // ThreadLocalValueHolderBases. - } - - private: - // In a particular thread, maps a ThreadLocal object to its value. - typedef std::map > - ThreadLocalValues; - // Stores all ThreadIdToThreadLocals having values in a thread, indexed by - // thread's ID. - typedef std::map ThreadIdToThreadLocals; - - // Holds the thread id and thread handle that we pass from - // StartWatcherThreadFor to WatcherThreadFunc. - typedef std::pair ThreadIdAndHandle; - - static void StartWatcherThreadFor(DWORD thread_id) { - // The returned handle will be kept in thread_map and closed by - // watcher_thread in WatcherThreadFunc. - HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION, - FALSE, - thread_id); - GTEST_CHECK_(thread != nullptr); - // We need to pass a valid thread ID pointer into CreateThread for it - // to work correctly under Win98. - DWORD watcher_thread_id; - HANDLE watcher_thread = ::CreateThread( - nullptr, // Default security. - 0, // Default stack size - &ThreadLocalRegistryImpl::WatcherThreadFunc, - reinterpret_cast(new ThreadIdAndHandle(thread_id, thread)), - CREATE_SUSPENDED, &watcher_thread_id); - GTEST_CHECK_(watcher_thread != nullptr); - // Give the watcher thread the same priority as ours to avoid being - // blocked by it. - ::SetThreadPriority(watcher_thread, - ::GetThreadPriority(::GetCurrentThread())); - ::ResumeThread(watcher_thread); - ::CloseHandle(watcher_thread); - } - - // Monitors exit from a given thread and notifies those - // ThreadIdToThreadLocals about thread termination. - static DWORD WINAPI WatcherThreadFunc(LPVOID param) { - const ThreadIdAndHandle* tah = - reinterpret_cast(param); - GTEST_CHECK_( - ::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0); - OnThreadExit(tah->first); - ::CloseHandle(tah->second); - delete tah; - return 0; - } - - // Returns map of thread local instances. - static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() { - mutex_.AssertHeld(); -#ifdef _MSC_VER - MemoryIsNotDeallocated memory_is_not_deallocated; -#endif // _MSC_VER - static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals(); - return map; - } - - // Protects access to GetThreadLocalsMapLocked() and its return value. - static Mutex mutex_; - // Protects access to GetThreadMapLocked() and its return value. - static Mutex thread_map_mutex_; -}; - -Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex); -Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex); - -ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread( - const ThreadLocalBase* thread_local_instance) { - return ThreadLocalRegistryImpl::GetValueOnCurrentThread( - thread_local_instance); -} - -void ThreadLocalRegistry::OnThreadLocalDestroyed( - const ThreadLocalBase* thread_local_instance) { - ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance); -} - -#endif // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS - -#if GTEST_USES_POSIX_RE - -// Implements RE. Currently only needed for death tests. - -RE::~RE() { - if (is_valid_) { - // regfree'ing an invalid regex might crash because the content - // of the regex is undefined. Since the regex's are essentially - // the same, one cannot be valid (or invalid) without the other - // being so too. - regfree(&partial_regex_); - regfree(&full_regex_); - } - free(const_cast(pattern_)); -} - -// Returns true iff regular expression re matches the entire str. -bool RE::FullMatch(const char* str, const RE& re) { - if (!re.is_valid_) return false; - - regmatch_t match; - return regexec(&re.full_regex_, str, 1, &match, 0) == 0; -} - -// Returns true iff regular expression re matches a substring of str -// (including str itself). -bool RE::PartialMatch(const char* str, const RE& re) { - if (!re.is_valid_) return false; - - regmatch_t match; - return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; -} - -// Initializes an RE from its string representation. -void RE::Init(const char* regex) { - pattern_ = posix::StrDup(regex); - - // Reserves enough bytes to hold the regular expression used for a - // full match. - const size_t full_regex_len = strlen(regex) + 10; - char* const full_pattern = new char[full_regex_len]; - - snprintf(full_pattern, full_regex_len, "^(%s)$", regex); - is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0; - // We want to call regcomp(&partial_regex_, ...) even if the - // previous expression returns false. Otherwise partial_regex_ may - // not be properly initialized can may cause trouble when it's - // freed. - // - // Some implementation of POSIX regex (e.g. on at least some - // versions of Cygwin) doesn't accept the empty string as a valid - // regex. We change it to an equivalent form "()" to be safe. - if (is_valid_) { - const char* const partial_regex = (*regex == '\0') ? "()" : regex; - is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0; - } - EXPECT_TRUE(is_valid_) - << "Regular expression \"" << regex - << "\" is not a valid POSIX Extended regular expression."; - - delete[] full_pattern; -} - -#elif GTEST_USES_SIMPLE_RE - -// Returns true iff ch appears anywhere in str (excluding the -// terminating '\0' character). -bool IsInSet(char ch, const char* str) { - return ch != '\0' && strchr(str, ch) != nullptr; -} - -// Returns true iff ch belongs to the given classification. Unlike -// similar functions in , these aren't affected by the -// current locale. -bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } -bool IsAsciiPunct(char ch) { - return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); -} -bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } -bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } -bool IsAsciiWordChar(char ch) { - return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || - ('0' <= ch && ch <= '9') || ch == '_'; -} - -// Returns true iff "\\c" is a supported escape sequence. -bool IsValidEscape(char c) { - return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); -} - -// Returns true iff the given atom (specified by escaped and pattern) -// matches ch. The result is undefined if the atom is invalid. -bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { - if (escaped) { // "\\p" where p is pattern_char. - switch (pattern_char) { - case 'd': return IsAsciiDigit(ch); - case 'D': return !IsAsciiDigit(ch); - case 'f': return ch == '\f'; - case 'n': return ch == '\n'; - case 'r': return ch == '\r'; - case 's': return IsAsciiWhiteSpace(ch); - case 'S': return !IsAsciiWhiteSpace(ch); - case 't': return ch == '\t'; - case 'v': return ch == '\v'; - case 'w': return IsAsciiWordChar(ch); - case 'W': return !IsAsciiWordChar(ch); - } - return IsAsciiPunct(pattern_char) && pattern_char == ch; - } - - return (pattern_char == '.' && ch != '\n') || pattern_char == ch; -} - -// Helper function used by ValidateRegex() to format error messages. -static std::string FormatRegexSyntaxError(const char* regex, int index) { - return (Message() << "Syntax error at index " << index - << " in simple regular expression \"" << regex << "\": ").GetString(); -} - -// Generates non-fatal failures and returns false if regex is invalid; -// otherwise returns true. -bool ValidateRegex(const char* regex) { - if (regex == nullptr) { - ADD_FAILURE() << "NULL is not a valid simple regular expression."; - return false; - } - - bool is_valid = true; - - // True iff ?, *, or + can follow the previous atom. - bool prev_repeatable = false; - for (int i = 0; regex[i]; i++) { - if (regex[i] == '\\') { // An escape sequence - i++; - if (regex[i] == '\0') { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) - << "'\\' cannot appear at the end."; - return false; - } - - if (!IsValidEscape(regex[i])) { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) - << "invalid escape sequence \"\\" << regex[i] << "\"."; - is_valid = false; - } - prev_repeatable = true; - } else { // Not an escape sequence. - const char ch = regex[i]; - - if (ch == '^' && i > 0) { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i) - << "'^' can only appear at the beginning."; - is_valid = false; - } else if (ch == '$' && regex[i + 1] != '\0') { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i) - << "'$' can only appear at the end."; - is_valid = false; - } else if (IsInSet(ch, "()[]{}|")) { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i) - << "'" << ch << "' is unsupported."; - is_valid = false; - } else if (IsRepeat(ch) && !prev_repeatable) { - ADD_FAILURE() << FormatRegexSyntaxError(regex, i) - << "'" << ch << "' can only follow a repeatable token."; - is_valid = false; - } - - prev_repeatable = !IsInSet(ch, "^$?*+"); - } - } - - return is_valid; -} - -// Matches a repeated regex atom followed by a valid simple regular -// expression. The regex atom is defined as c if escaped is false, -// or \c otherwise. repeat is the repetition meta character (?, *, -// or +). The behavior is undefined if str contains too many -// characters to be indexable by size_t, in which case the test will -// probably time out anyway. We are fine with this limitation as -// std::string has it too. -bool MatchRepetitionAndRegexAtHead( - bool escaped, char c, char repeat, const char* regex, - const char* str) { - const size_t min_count = (repeat == '+') ? 1 : 0; - const size_t max_count = (repeat == '?') ? 1 : - static_cast(-1) - 1; - // We cannot call numeric_limits::max() as it conflicts with the - // max() macro on Windows. - - for (size_t i = 0; i <= max_count; ++i) { - // We know that the atom matches each of the first i characters in str. - if (i >= min_count && MatchRegexAtHead(regex, str + i)) { - // We have enough matches at the head, and the tail matches too. - // Since we only care about *whether* the pattern matches str - // (as opposed to *how* it matches), there is no need to find a - // greedy match. - return true; - } - if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) - return false; - } - return false; -} - -// Returns true iff regex matches a prefix of str. regex must be a -// valid simple regular expression and not start with "^", or the -// result is undefined. -bool MatchRegexAtHead(const char* regex, const char* str) { - if (*regex == '\0') // An empty regex matches a prefix of anything. - return true; - - // "$" only matches the end of a string. Note that regex being - // valid guarantees that there's nothing after "$" in it. - if (*regex == '$') - return *str == '\0'; - - // Is the first thing in regex an escape sequence? - const bool escaped = *regex == '\\'; - if (escaped) - ++regex; - if (IsRepeat(regex[1])) { - // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so - // here's an indirect recursion. It terminates as the regex gets - // shorter in each recursion. - return MatchRepetitionAndRegexAtHead( - escaped, regex[0], regex[1], regex + 2, str); - } else { - // regex isn't empty, isn't "$", and doesn't start with a - // repetition. We match the first atom of regex with the first - // character of str and recurse. - return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && - MatchRegexAtHead(regex + 1, str + 1); - } -} - -// Returns true iff regex matches any substring of str. regex must be -// a valid simple regular expression, or the result is undefined. -// -// The algorithm is recursive, but the recursion depth doesn't exceed -// the regex length, so we won't need to worry about running out of -// stack space normally. In rare cases the time complexity can be -// exponential with respect to the regex length + the string length, -// but usually it's must faster (often close to linear). -bool MatchRegexAnywhere(const char* regex, const char* str) { - if (regex == nullptr || str == nullptr) return false; - - if (*regex == '^') - return MatchRegexAtHead(regex + 1, str); - - // A successful match can be anywhere in str. - do { - if (MatchRegexAtHead(regex, str)) - return true; - } while (*str++ != '\0'); - return false; -} - -// Implements the RE class. - -RE::~RE() { - free(const_cast(pattern_)); - free(const_cast(full_pattern_)); -} - -// Returns true iff regular expression re matches the entire str. -bool RE::FullMatch(const char* str, const RE& re) { - return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str); -} - -// Returns true iff regular expression re matches a substring of str -// (including str itself). -bool RE::PartialMatch(const char* str, const RE& re) { - return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str); -} - -// Initializes an RE from its string representation. -void RE::Init(const char* regex) { - pattern_ = full_pattern_ = nullptr; - if (regex != nullptr) { - pattern_ = posix::StrDup(regex); - } - - is_valid_ = ValidateRegex(regex); - if (!is_valid_) { - // No need to calculate the full pattern when the regex is invalid. - return; - } - - const size_t len = strlen(regex); - // Reserves enough bytes to hold the regular expression used for a - // full match: we need space to prepend a '^', append a '$', and - // terminate the string with '\0'. - char* buffer = static_cast(malloc(len + 3)); - full_pattern_ = buffer; - - if (*regex != '^') - *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'. - - // We don't use snprintf or strncpy, as they trigger a warning when - // compiled with VC++ 8.0. - memcpy(buffer, regex, len); - buffer += len; - - if (len == 0 || regex[len - 1] != '$') - *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'. - - *buffer = '\0'; -} - -#endif // GTEST_USES_POSIX_RE - -const char kUnknownFile[] = "unknown file"; - -// Formats a source file path and a line number as they would appear -// in an error message from the compiler used to compile this code. -GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { - const std::string file_name(file == nullptr ? kUnknownFile : file); - - if (line < 0) { - return file_name + ":"; - } -#ifdef _MSC_VER - return file_name + "(" + StreamableToString(line) + "):"; -#else - return file_name + ":" + StreamableToString(line) + ":"; -#endif // _MSC_VER -} - -// Formats a file location for compiler-independent XML output. -// Although this function is not platform dependent, we put it next to -// FormatFileLocation in order to contrast the two functions. -// Note that FormatCompilerIndependentFileLocation() does NOT append colon -// to the file location it produces, unlike FormatFileLocation(). -GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( - const char* file, int line) { - const std::string file_name(file == nullptr ? kUnknownFile : file); - - if (line < 0) - return file_name; - else - return file_name + ":" + StreamableToString(line); -} - -GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) - : severity_(severity) { - const char* const marker = - severity == GTEST_INFO ? "[ INFO ]" : - severity == GTEST_WARNING ? "[WARNING]" : - severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; - GetStream() << ::std::endl << marker << " " - << FormatFileLocation(file, line).c_str() << ": "; -} - -// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. -GTestLog::~GTestLog() { - GetStream() << ::std::endl; - if (severity_ == GTEST_FATAL) { - fflush(stderr); - posix::Abort(); - } -} - -// Disable Microsoft deprecation warnings for POSIX functions called from -// this class (creat, dup, dup2, and close) -GTEST_DISABLE_MSC_DEPRECATED_PUSH_() - -#if GTEST_HAS_STREAM_REDIRECTION - -// Object that captures an output stream (stdout/stderr). -class CapturedStream { - public: - // The ctor redirects the stream to a temporary file. - explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { -# if GTEST_OS_WINDOWS - char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT - char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT - - ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); - const UINT success = ::GetTempFileNameA(temp_dir_path, - "gtest_redir", - 0, // Generate unique file name. - temp_file_path); - GTEST_CHECK_(success != 0) - << "Unable to create a temporary file in " << temp_dir_path; - const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); - GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " - << temp_file_path; - filename_ = temp_file_path; -# else - // There's no guarantee that a test has write access to the current - // directory, so we create the temporary file in the /tmp directory - // instead. We use /tmp on most systems, and /sdcard on Android. - // That's because Android doesn't have /tmp. -# if GTEST_OS_LINUX_ANDROID - // Note: Android applications are expected to call the framework's - // Context.getExternalStorageDirectory() method through JNI to get - // the location of the world-writable SD Card directory. However, - // this requires a Context handle, which cannot be retrieved - // globally from native code. Doing so also precludes running the - // code as part of a regular standalone executable, which doesn't - // run in a Dalvik process (e.g. when running it through 'adb shell'). - // - // The location /sdcard is directly accessible from native code - // and is the only location (unofficially) supported by the Android - // team. It's generally a symlink to the real SD Card mount point - // which can be /mnt/sdcard, /mnt/sdcard0, /system/media/sdcard, or - // other OEM-customized locations. Never rely on these, and always - // use /sdcard. - char name_template[] = "/sdcard/gtest_captured_stream.XXXXXX"; -# else - char name_template[] = "/tmp/captured_stream.XXXXXX"; -# endif // GTEST_OS_LINUX_ANDROID - const int captured_fd = mkstemp(name_template); - if (captured_fd == -1) { - GTEST_LOG_(WARNING) - << "Failed to create tmp file " << name_template - << " for test; does the test have access to the /tmp directory?"; - } - filename_ = name_template; -# endif // GTEST_OS_WINDOWS - fflush(nullptr); - dup2(captured_fd, fd_); - close(captured_fd); - } - - ~CapturedStream() { - remove(filename_.c_str()); - } - - std::string GetCapturedString() { - if (uncaptured_fd_ != -1) { - // Restores the original stream. - fflush(nullptr); - dup2(uncaptured_fd_, fd_); - close(uncaptured_fd_); - uncaptured_fd_ = -1; - } - - FILE* const file = posix::FOpen(filename_.c_str(), "r"); - if (file == nullptr) { - GTEST_LOG_(FATAL) << "Failed to open tmp file " << filename_ - << " for capturing stream."; - } - const std::string content = ReadEntireFile(file); - posix::FClose(file); - return content; - } - - private: - const int fd_; // A stream to capture. - int uncaptured_fd_; - // Name of the temporary file holding the stderr output. - ::std::string filename_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); -}; - -GTEST_DISABLE_MSC_DEPRECATED_POP_() - -static CapturedStream* g_captured_stderr = nullptr; -static CapturedStream* g_captured_stdout = nullptr; - -// Starts capturing an output stream (stdout/stderr). -static void CaptureStream(int fd, const char* stream_name, - CapturedStream** stream) { - if (*stream != nullptr) { - GTEST_LOG_(FATAL) << "Only one " << stream_name - << " capturer can exist at a time."; - } - *stream = new CapturedStream(fd); -} - -// Stops capturing the output stream and returns the captured string. -static std::string GetCapturedStream(CapturedStream** captured_stream) { - const std::string content = (*captured_stream)->GetCapturedString(); - - delete *captured_stream; - *captured_stream = nullptr; - - return content; -} - -// Starts capturing stdout. -void CaptureStdout() { - CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); -} - -// Starts capturing stderr. -void CaptureStderr() { - CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); -} - -// Stops capturing stdout and returns the captured string. -std::string GetCapturedStdout() { - return GetCapturedStream(&g_captured_stdout); -} - -// Stops capturing stderr and returns the captured string. -std::string GetCapturedStderr() { - return GetCapturedStream(&g_captured_stderr); -} - -#endif // GTEST_HAS_STREAM_REDIRECTION - - - - - -size_t GetFileSize(FILE* file) { - fseek(file, 0, SEEK_END); - return static_cast(ftell(file)); -} - -std::string ReadEntireFile(FILE* file) { - const size_t file_size = GetFileSize(file); - char* const buffer = new char[file_size]; - - size_t bytes_last_read = 0; // # of bytes read in the last fread() - size_t bytes_read = 0; // # of bytes read so far - - fseek(file, 0, SEEK_SET); - - // Keeps reading the file until we cannot read further or the - // pre-determined file size is reached. - do { - bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); - bytes_read += bytes_last_read; - } while (bytes_last_read > 0 && bytes_read < file_size); - - const std::string content(buffer, bytes_read); - delete[] buffer; - - return content; -} - -#if GTEST_HAS_DEATH_TEST -static const std::vector* g_injected_test_argvs = - nullptr; // Owned. - -std::vector GetInjectableArgvs() { - if (g_injected_test_argvs != nullptr) { - return *g_injected_test_argvs; - } - return GetArgvs(); -} - -void SetInjectableArgvs(const std::vector* new_argvs) { - if (g_injected_test_argvs != new_argvs) delete g_injected_test_argvs; - g_injected_test_argvs = new_argvs; -} - -void SetInjectableArgvs(const std::vector& new_argvs) { - SetInjectableArgvs( - new std::vector(new_argvs.begin(), new_argvs.end())); -} - -void ClearInjectableArgvs() { - delete g_injected_test_argvs; - g_injected_test_argvs = nullptr; -} -#endif // GTEST_HAS_DEATH_TEST - -#if GTEST_OS_WINDOWS_MOBILE -namespace posix { -void Abort() { - DebugBreak(); - TerminateProcess(GetCurrentProcess(), 1); -} -} // namespace posix -#endif // GTEST_OS_WINDOWS_MOBILE - -// Returns the name of the environment variable corresponding to the -// given flag. For example, FlagToEnvVar("foo") will return -// "GTEST_FOO" in the open-source version. -static std::string FlagToEnvVar(const char* flag) { - const std::string full_flag = - (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); - - Message env_var; - for (size_t i = 0; i != full_flag.length(); i++) { - env_var << ToUpper(full_flag.c_str()[i]); - } - - return env_var.GetString(); -} - -// Parses 'str' for a 32-bit signed integer. If successful, writes -// the result to *value and returns true; otherwise leaves *value -// unchanged and returns false. -bool ParseInt32(const Message& src_text, const char* str, Int32* value) { - // Parses the environment variable as a decimal integer. - char* end = nullptr; - const long long_value = strtol(str, &end, 10); // NOLINT - - // Has strtol() consumed all characters in the string? - if (*end != '\0') { - // No - an invalid character was encountered. - Message msg; - msg << "WARNING: " << src_text - << " is expected to be a 32-bit integer, but actually" - << " has value \"" << str << "\".\n"; - printf("%s", msg.GetString().c_str()); - fflush(stdout); - return false; - } - - // Is the parsed value in the range of an Int32? - const Int32 result = static_cast(long_value); - if (long_value == LONG_MAX || long_value == LONG_MIN || - // The parsed value overflows as a long. (strtol() returns - // LONG_MAX or LONG_MIN when the input overflows.) - result != long_value - // The parsed value overflows as an Int32. - ) { - Message msg; - msg << "WARNING: " << src_text - << " is expected to be a 32-bit integer, but actually" - << " has value " << str << ", which overflows.\n"; - printf("%s", msg.GetString().c_str()); - fflush(stdout); - return false; - } - - *value = result; - return true; -} - -// Reads and returns the Boolean environment variable corresponding to -// the given flag; if it's not set, returns default_value. -// -// The value is considered true iff it's not "0". -bool BoolFromGTestEnv(const char* flag, bool default_value) { -#if defined(GTEST_GET_BOOL_FROM_ENV_) - return GTEST_GET_BOOL_FROM_ENV_(flag, default_value); -#else - const std::string env_var = FlagToEnvVar(flag); - const char* const string_value = posix::GetEnv(env_var.c_str()); - return string_value == nullptr ? default_value - : strcmp(string_value, "0") != 0; -#endif // defined(GTEST_GET_BOOL_FROM_ENV_) -} - -// Reads and returns a 32-bit integer stored in the environment -// variable corresponding to the given flag; if it isn't set or -// doesn't represent a valid 32-bit integer, returns default_value. -Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { -#if defined(GTEST_GET_INT32_FROM_ENV_) - return GTEST_GET_INT32_FROM_ENV_(flag, default_value); -#else - const std::string env_var = FlagToEnvVar(flag); - const char* const string_value = posix::GetEnv(env_var.c_str()); - if (string_value == nullptr) { - // The environment variable is not set. - return default_value; - } - - Int32 result = default_value; - if (!ParseInt32(Message() << "Environment variable " << env_var, - string_value, &result)) { - printf("The default value %s is used.\n", - (Message() << default_value).GetString().c_str()); - fflush(stdout); - return default_value; - } - - return result; -#endif // defined(GTEST_GET_INT32_FROM_ENV_) -} - -// As a special case for the 'output' flag, if GTEST_OUTPUT is not -// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build -// system. The value of XML_OUTPUT_FILE is a filename without the -// "xml:" prefix of GTEST_OUTPUT. -// Note that this is meant to be called at the call site so it does -// not check that the flag is 'output' -// In essence this checks an env variable called XML_OUTPUT_FILE -// and if it is set we prepend "xml:" to its value, if it not set we return "" -std::string OutputFlagAlsoCheckEnvVar(){ - std::string default_value_for_output_flag = ""; - const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE"); - if (nullptr != xml_output_file_env) { - default_value_for_output_flag = std::string("xml:") + xml_output_file_env; - } - return default_value_for_output_flag; -} - -// Reads and returns the string environment variable corresponding to -// the given flag; if it's not set, returns default_value. -const char* StringFromGTestEnv(const char* flag, const char* default_value) { -#if defined(GTEST_GET_STRING_FROM_ENV_) - return GTEST_GET_STRING_FROM_ENV_(flag, default_value); -#else - const std::string env_var = FlagToEnvVar(flag); - const char* const value = posix::GetEnv(env_var.c_str()); - return value == nullptr ? default_value : value; -#endif // defined(GTEST_GET_STRING_FROM_ENV_) -} - -} // namespace internal -} // namespace testing -// Copyright 2007, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -// Google Test - The Google C++ Testing and Mocking Framework -// -// This file implements a universal value printer that can print a -// value of any type T: -// -// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); -// -// It uses the << operator when possible, and prints the bytes in the -// object otherwise. A user can override its behavior for a class -// type Foo by defining either operator<<(::std::ostream&, const Foo&) -// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that -// defines Foo. - -#include -#include -#include -#include // NOLINT -#include - -namespace testing { - -namespace { - -using ::std::ostream; - -// Prints a segment of bytes in the given object. -GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ -GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ -void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, - size_t count, ostream* os) { - char text[5] = ""; - for (size_t i = 0; i != count; i++) { - const size_t j = start + i; - if (i != 0) { - // Organizes the bytes into groups of 2 for easy parsing by - // human. - if ((j % 2) == 0) - *os << ' '; - else - *os << '-'; - } - GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]); - *os << text; - } -} - -// Prints the bytes in the given value to the given ostream. -void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, - ostream* os) { - // Tells the user how big the object is. - *os << count << "-byte object <"; - - const size_t kThreshold = 132; - const size_t kChunkSize = 64; - // If the object size is bigger than kThreshold, we'll have to omit - // some details by printing only the first and the last kChunkSize - // bytes. - if (count < kThreshold) { - PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); - } else { - PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); - *os << " ... "; - // Rounds up to 2-byte boundary. - const size_t resume_pos = (count - kChunkSize + 1)/2*2; - PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); - } - *os << ">"; -} - -} // namespace - -namespace internal2 { - -// Delegates to PrintBytesInObjectToImpl() to print the bytes in the -// given object. The delegation simplifies the implementation, which -// uses the << operator and thus is easier done outside of the -// ::testing::internal namespace, which contains a << operator that -// sometimes conflicts with the one in STL. -void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, - ostream* os) { - PrintBytesInObjectToImpl(obj_bytes, count, os); -} - -} // namespace internal2 - -namespace internal { - -// Depending on the value of a char (or wchar_t), we print it in one -// of three formats: -// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), -// - as a hexadecimal escape sequence (e.g. '\x7F'), or -// - as a special escape sequence (e.g. '\r', '\n'). -enum CharFormat { - kAsIs, - kHexEscape, - kSpecialEscape -}; - -// Returns true if c is a printable ASCII character. We test the -// value of c directly instead of calling isprint(), which is buggy on -// Windows Mobile. -inline bool IsPrintableAscii(wchar_t c) { - return 0x20 <= c && c <= 0x7E; -} - -// Prints a wide or narrow char c as a character literal without the -// quotes, escaping it when necessary; returns how c was formatted. -// The template argument UnsignedChar is the unsigned version of Char, -// which is the type of c. -template -static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { - wchar_t w_c = static_cast(c); - switch (w_c) { - case L'\0': - *os << "\\0"; - break; - case L'\'': - *os << "\\'"; - break; - case L'\\': - *os << "\\\\"; - break; - case L'\a': - *os << "\\a"; - break; - case L'\b': - *os << "\\b"; - break; - case L'\f': - *os << "\\f"; - break; - case L'\n': - *os << "\\n"; - break; - case L'\r': - *os << "\\r"; - break; - case L'\t': - *os << "\\t"; - break; - case L'\v': - *os << "\\v"; - break; - default: - if (IsPrintableAscii(w_c)) { - *os << static_cast(c); - return kAsIs; - } else { - ostream::fmtflags flags = os->flags(); - *os << "\\x" << std::hex << std::uppercase - << static_cast(static_cast(c)); - os->flags(flags); - return kHexEscape; - } - } - return kSpecialEscape; -} - -// Prints a wchar_t c as if it's part of a string literal, escaping it when -// necessary; returns how c was formatted. -static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { - switch (c) { - case L'\'': - *os << "'"; - return kAsIs; - case L'"': - *os << "\\\""; - return kSpecialEscape; - default: - return PrintAsCharLiteralTo(c, os); - } -} - -// Prints a char c as if it's part of a string literal, escaping it when -// necessary; returns how c was formatted. -static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { - return PrintAsStringLiteralTo( - static_cast(static_cast(c)), os); -} - -// Prints a wide or narrow character c and its code. '\0' is printed -// as "'\\0'", other unprintable characters are also properly escaped -// using the standard C++ escape sequence. The template argument -// UnsignedChar is the unsigned version of Char, which is the type of c. -template -void PrintCharAndCodeTo(Char c, ostream* os) { - // First, print c as a literal in the most readable form we can find. - *os << ((sizeof(c) > 1) ? "L'" : "'"); - const CharFormat format = PrintAsCharLiteralTo(c, os); - *os << "'"; - - // To aid user debugging, we also print c's code in decimal, unless - // it's 0 (in which case c was printed as '\\0', making the code - // obvious). - if (c == 0) - return; - *os << " (" << static_cast(c); - - // For more convenience, we print c's code again in hexadecimal, - // unless c was already printed in the form '\x##' or the code is in - // [1, 9]. - if (format == kHexEscape || (1 <= c && c <= 9)) { - // Do nothing. - } else { - *os << ", 0x" << String::FormatHexInt(static_cast(c)); - } - *os << ")"; -} - -void PrintTo(unsigned char c, ::std::ostream* os) { - PrintCharAndCodeTo(c, os); -} -void PrintTo(signed char c, ::std::ostream* os) { - PrintCharAndCodeTo(c, os); -} - -// Prints a wchar_t as a symbol if it is printable or as its internal -// code otherwise and also as its code. L'\0' is printed as "L'\\0'". -void PrintTo(wchar_t wc, ostream* os) { - PrintCharAndCodeTo(wc, os); -} - -// Prints the given array of characters to the ostream. CharType must be either -// char or wchar_t. -// The array starts at begin, the length is len, it may include '\0' characters -// and may not be NUL-terminated. -template -GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ -GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ -static CharFormat PrintCharsAsStringTo( - const CharType* begin, size_t len, ostream* os) { - const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\""; - *os << kQuoteBegin; - bool is_previous_hex = false; - CharFormat print_format = kAsIs; - for (size_t index = 0; index < len; ++index) { - const CharType cur = begin[index]; - if (is_previous_hex && IsXDigit(cur)) { - // Previous character is of '\x..' form and this character can be - // interpreted as another hexadecimal digit in its number. Break string to - // disambiguate. - *os << "\" " << kQuoteBegin; - } - is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; - // Remember if any characters required hex escaping. - if (is_previous_hex) { - print_format = kHexEscape; - } - } - *os << "\""; - return print_format; -} - -// Prints a (const) char/wchar_t array of 'len' elements, starting at address -// 'begin'. CharType must be either char or wchar_t. -template -GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ -GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ -static void UniversalPrintCharArray( - const CharType* begin, size_t len, ostream* os) { - // The code - // const char kFoo[] = "foo"; - // generates an array of 4, not 3, elements, with the last one being '\0'. - // - // Therefore when printing a char array, we don't print the last element if - // it's '\0', such that the output matches the string literal as it's - // written in the source code. - if (len > 0 && begin[len - 1] == '\0') { - PrintCharsAsStringTo(begin, len - 1, os); - return; - } - - // If, however, the last element in the array is not '\0', e.g. - // const char kFoo[] = { 'f', 'o', 'o' }; - // we must print the entire array. We also print a message to indicate - // that the array is not NUL-terminated. - PrintCharsAsStringTo(begin, len, os); - *os << " (no terminating NUL)"; -} - -// Prints a (const) char array of 'len' elements, starting at address 'begin'. -void UniversalPrintArray(const char* begin, size_t len, ostream* os) { - UniversalPrintCharArray(begin, len, os); -} - -// Prints a (const) wchar_t array of 'len' elements, starting at address -// 'begin'. -void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { - UniversalPrintCharArray(begin, len, os); -} - -// Prints the given C string to the ostream. -void PrintTo(const char* s, ostream* os) { - if (s == nullptr) { - *os << "NULL"; - } else { - *os << ImplicitCast_(s) << " pointing to "; - PrintCharsAsStringTo(s, strlen(s), os); - } -} - -// MSVC compiler can be configured to define whar_t as a typedef -// of unsigned short. Defining an overload for const wchar_t* in that case -// would cause pointers to unsigned shorts be printed as wide strings, -// possibly accessing more memory than intended and causing invalid -// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when -// wchar_t is implemented as a native type. -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) -// Prints the given wide C string to the ostream. -void PrintTo(const wchar_t* s, ostream* os) { - if (s == nullptr) { - *os << "NULL"; - } else { - *os << ImplicitCast_(s) << " pointing to "; - PrintCharsAsStringTo(s, wcslen(s), os); - } -} -#endif // wchar_t is native - -namespace { - -bool ContainsUnprintableControlCodes(const char* str, size_t length) { - const unsigned char *s = reinterpret_cast(str); - - for (size_t i = 0; i < length; i++) { - unsigned char ch = *s++; - if (std::iscntrl(ch)) { - switch (ch) { - case '\t': - case '\n': - case '\r': - break; - default: - return true; - } - } - } - return false; -} - -bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t<= 0xbf; } - -bool IsValidUTF8(const char* str, size_t length) { - const unsigned char *s = reinterpret_cast(str); - - for (size_t i = 0; i < length;) { - unsigned char lead = s[i++]; - - if (lead <= 0x7f) { - continue; // single-byte character (ASCII) 0..7F - } - if (lead < 0xc2) { - return false; // trail byte or non-shortest form - } else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) { - ++i; // 2-byte character - } else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length && - IsUTF8TrailByte(s[i]) && - IsUTF8TrailByte(s[i + 1]) && - // check for non-shortest form and surrogate - (lead != 0xe0 || s[i] >= 0xa0) && - (lead != 0xed || s[i] < 0xa0)) { - i += 2; // 3-byte character - } else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length && - IsUTF8TrailByte(s[i]) && - IsUTF8TrailByte(s[i + 1]) && - IsUTF8TrailByte(s[i + 2]) && - // check for non-shortest form - (lead != 0xf0 || s[i] >= 0x90) && - (lead != 0xf4 || s[i] < 0x90)) { - i += 3; // 4-byte character - } else { - return false; - } - } - return true; -} - -void ConditionalPrintAsText(const char* str, size_t length, ostream* os) { - if (!ContainsUnprintableControlCodes(str, length) && - IsValidUTF8(str, length)) { - *os << "\n As Text: \"" << str << "\""; - } -} - -} // anonymous namespace - -void PrintStringTo(const ::std::string& s, ostream* os) { - if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) { - if (GTEST_FLAG(print_utf8)) { - ConditionalPrintAsText(s.data(), s.size(), os); - } - } -} - -#if GTEST_HAS_STD_WSTRING -void PrintWideStringTo(const ::std::wstring& s, ostream* os) { - PrintCharsAsStringTo(s.data(), s.size(), os); -} -#endif // GTEST_HAS_STD_WSTRING - -} // namespace internal - -} // namespace testing -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// The Google C++ Testing and Mocking Framework (Google Test) - - -namespace testing { - -using internal::GetUnitTestImpl; - -// Gets the summary of the failure message by omitting the stack trace -// in it. -std::string TestPartResult::ExtractSummary(const char* message) { - const char* const stack_trace = strstr(message, internal::kStackTraceMarker); - return stack_trace == nullptr ? message : std::string(message, stack_trace); -} - -// Prints a TestPartResult object. -std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { - return os << result.file_name() << ":" << result.line_number() << ": " - << (result.type() == TestPartResult::kSuccess - ? "Success" - : result.type() == TestPartResult::kSkip - ? "Skipped" - : result.type() == TestPartResult::kFatalFailure - ? "Fatal failure" - : "Non-fatal failure") - << ":\n" - << result.message() << std::endl; -} - -// Appends a TestPartResult to the array. -void TestPartResultArray::Append(const TestPartResult& result) { - array_.push_back(result); -} - -// Returns the TestPartResult at the given index (0-based). -const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { - if (index < 0 || index >= size()) { - printf("\nInvalid index (%d) into TestPartResultArray.\n", index); - internal::posix::Abort(); - } - - return array_[static_cast(index)]; -} - -// Returns the number of TestPartResult objects in the array. -int TestPartResultArray::size() const { - return static_cast(array_.size()); -} - -namespace internal { - -HasNewFatalFailureHelper::HasNewFatalFailureHelper() - : has_new_fatal_failure_(false), - original_reporter_(GetUnitTestImpl()-> - GetTestPartResultReporterForCurrentThread()) { - GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); -} - -HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { - GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( - original_reporter_); -} - -void HasNewFatalFailureHelper::ReportTestPartResult( - const TestPartResult& result) { - if (result.fatally_failed()) - has_new_fatal_failure_ = true; - original_reporter_->ReportTestPartResult(result); -} - -} // namespace internal - -} // namespace testing -// Copyright 2008 Google Inc. -// All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - - -namespace testing { -namespace internal { - -#if GTEST_HAS_TYPED_TEST_P - -// Skips to the first non-space char in str. Returns an empty string if str -// contains only whitespace characters. -static const char* SkipSpaces(const char* str) { - while (IsSpace(*str)) - str++; - return str; -} - -static std::vector SplitIntoTestNames(const char* src) { - std::vector name_vec; - src = SkipSpaces(src); - for (; src != nullptr; src = SkipComma(src)) { - name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src))); - } - return name_vec; -} - -// Verifies that registered_tests match the test names in -// registered_tests_; returns registered_tests if successful, or -// aborts the program otherwise. -const char* TypedTestSuitePState::VerifyRegisteredTestNames( - const char* file, int line, const char* registered_tests) { - typedef RegisteredTestsMap::const_iterator RegisteredTestIter; - registered_ = true; - - std::vector name_vec = SplitIntoTestNames(registered_tests); - - Message errors; - - std::set tests; - for (std::vector::const_iterator name_it = name_vec.begin(); - name_it != name_vec.end(); ++name_it) { - const std::string& name = *name_it; - if (tests.count(name) != 0) { - errors << "Test " << name << " is listed more than once.\n"; - continue; - } - - bool found = false; - for (RegisteredTestIter it = registered_tests_.begin(); - it != registered_tests_.end(); - ++it) { - if (name == it->first) { - found = true; - break; - } - } - - if (found) { - tests.insert(name); - } else { - errors << "No test named " << name - << " can be found in this test suite.\n"; - } - } - - for (RegisteredTestIter it = registered_tests_.begin(); - it != registered_tests_.end(); - ++it) { - if (tests.count(it->first) == 0) { - errors << "You forgot to list test " << it->first << ".\n"; - } - } - - const std::string& errors_str = errors.GetString(); - if (errors_str != "") { - fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), - errors_str.c_str()); - fflush(stderr); - posix::Abort(); - } - - return registered_tests; -} - -#endif // GTEST_HAS_TYPED_TEST_P - -} // namespace internal -} // namespace testing diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest.h b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest.h deleted file mode 100644 index 844c9b7cd..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/gtest/gtest.h +++ /dev/null @@ -1,14916 +0,0 @@ -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file defines the public API for Google Test. It should be -// included by any test program that uses Google Test. -// -// IMPORTANT NOTE: Due to limitation of the C++ language, we have to -// leave some internal implementation details in this header file. -// They are clearly marked by comments like this: -// -// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -// -// Such code is NOT meant to be used by a user directly, and is subject -// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user -// program! -// -// Acknowledgment: Google Test borrowed the idea of automatic test -// registration from Barthelemy Dagenais' (barthelemy@prologique.com) -// easyUnit framework. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ -#define GTEST_INCLUDE_GTEST_GTEST_H_ - -#include -#include -#include -#include -#include -#include - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file declares functions and macros used internally by -// Google Test. They are subject to change without notice. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Low-level types and utilities for porting Google Test to various -// platforms. All macros ending with _ and symbols defined in an -// internal namespace are subject to change without notice. Code -// outside Google Test MUST NOT USE THEM DIRECTLY. Macros that don't -// end with _ are part of Google Test's public API and can be used by -// code outside Google Test. -// -// This file is fundamental to Google Test. All other Google Test source -// files are expected to #include this. Therefore, it cannot #include -// any other Google Test header. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ - -// Environment-describing macros -// ----------------------------- -// -// Google Test can be used in many different environments. Macros in -// this section tell Google Test what kind of environment it is being -// used in, such that Google Test can provide environment-specific -// features and implementations. -// -// Google Test tries to automatically detect the properties of its -// environment, so users usually don't need to worry about these -// macros. However, the automatic detection is not perfect. -// Sometimes it's necessary for a user to define some of the following -// macros in the build script to override Google Test's decisions. -// -// If the user doesn't define a macro in the list, Google Test will -// provide a default definition. After this header is #included, all -// macros in this list will be defined to either 1 or 0. -// -// Notes to maintainers: -// - Each macro here is a user-tweakable knob; do not grow the list -// lightly. -// - Use #if to key off these macros. Don't use #ifdef or "#if -// defined(...)", which will not work as these macros are ALWAYS -// defined. -// -// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) -// is/isn't available. -// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions -// are enabled. -// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular -// expressions are/aren't available. -// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that -// is/isn't available. -// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't -// enabled. -// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that -// std::wstring does/doesn't work (Google Test can -// be used where std::wstring is unavailable). -// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the -// compiler supports Microsoft's "Structured -// Exception Handling". -// GTEST_HAS_STREAM_REDIRECTION -// - Define it to 1/0 to indicate whether the -// platform supports I/O stream redirection using -// dup() and dup2(). -// GTEST_LINKED_AS_SHARED_LIBRARY -// - Define to 1 when compiling tests that use -// Google Test as a shared library (known as -// DLL on Windows). -// GTEST_CREATE_SHARED_LIBRARY -// - Define to 1 when compiling Google Test itself -// as a shared library. -// GTEST_DEFAULT_DEATH_TEST_STYLE -// - The default value of --gtest_death_test_style. -// The legacy default has been "fast" in the open -// source version since 2008. The recommended value -// is "threadsafe", and can be set in -// custom/gtest-port.h. - -// Platform-indicating macros -// -------------------------- -// -// Macros indicating the platform on which Google Test is being used -// (a macro is defined to 1 if compiled on the given platform; -// otherwise UNDEFINED -- it's never defined to 0.). Google Test -// defines these macros automatically. Code outside Google Test MUST -// NOT define them. -// -// GTEST_OS_AIX - IBM AIX -// GTEST_OS_CYGWIN - Cygwin -// GTEST_OS_DRAGONFLY - DragonFlyBSD -// GTEST_OS_FREEBSD - FreeBSD -// GTEST_OS_FUCHSIA - Fuchsia -// GTEST_OS_GNU_KFREEBSD - GNU/kFreeBSD -// GTEST_OS_HAIKU - Haiku -// GTEST_OS_HPUX - HP-UX -// GTEST_OS_LINUX - Linux -// GTEST_OS_LINUX_ANDROID - Google Android -// GTEST_OS_MAC - Mac OS X -// GTEST_OS_IOS - iOS -// GTEST_OS_NACL - Google Native Client (NaCl) -// GTEST_OS_NETBSD - NetBSD -// GTEST_OS_OPENBSD - OpenBSD -// GTEST_OS_OS2 - OS/2 -// GTEST_OS_QNX - QNX -// GTEST_OS_SOLARIS - Sun Solaris -// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) -// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop -// GTEST_OS_WINDOWS_MINGW - MinGW -// GTEST_OS_WINDOWS_MOBILE - Windows Mobile -// GTEST_OS_WINDOWS_PHONE - Windows Phone -// GTEST_OS_WINDOWS_RT - Windows Store App/WinRT -// GTEST_OS_ZOS - z/OS -// -// Among the platforms, Cygwin, Linux, Mac OS X, and Windows have the -// most stable support. Since core members of the Google Test project -// don't have access to other platforms, support for them may be less -// stable. If you notice any problems on your platform, please notify -// googletestframework@googlegroups.com (patches for fixing them are -// even more welcome!). -// -// It is possible that none of the GTEST_OS_* macros are defined. - -// Feature-indicating macros -// ------------------------- -// -// Macros indicating which Google Test features are available (a macro -// is defined to 1 if the corresponding feature is supported; -// otherwise UNDEFINED -- it's never defined to 0.). Google Test -// defines these macros automatically. Code outside Google Test MUST -// NOT define them. -// -// These macros are public so that portable tests can be written. -// Such tests typically surround code using a feature with an #if -// which controls that code. For example: -// -// #if GTEST_HAS_DEATH_TEST -// EXPECT_DEATH(DoSomethingDeadly()); -// #endif -// -// GTEST_HAS_DEATH_TEST - death tests -// GTEST_HAS_TYPED_TEST - typed tests -// GTEST_HAS_TYPED_TEST_P - type-parameterized tests -// GTEST_IS_THREADSAFE - Google Test is thread-safe. -// GOOGLETEST_CM0007 DO NOT DELETE -// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with -// GTEST_HAS_POSIX_RE (see above) which users can -// define themselves. -// GTEST_USES_SIMPLE_RE - our own simple regex is used; -// the above RE\b(s) are mutually exclusive. - -// Misc public macros -// ------------------ -// -// GTEST_FLAG(flag_name) - references the variable corresponding to -// the given Google Test flag. - -// Internal utilities -// ------------------ -// -// The following macros and utilities are for Google Test's INTERNAL -// use only. Code outside Google Test MUST NOT USE THEM DIRECTLY. -// -// Macros for basic C++ coding: -// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. -// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a -// variable don't have to be used. -// GTEST_DISALLOW_ASSIGN_ - disables operator=. -// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. -// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. -// GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is -// suppressed (constant conditional). -// GTEST_INTENTIONAL_CONST_COND_POP_ - finish code section where MSVC C4127 -// is suppressed. -// -// Synchronization: -// Mutex, MutexLock, ThreadLocal, GetThreadCount() -// - synchronization primitives. -// -// Template meta programming: -// IteratorTraits - partial implementation of std::iterator_traits, which -// is not available in libCstd when compiled with Sun C++. -// -// -// Regular expressions: -// RE - a simple regular expression class using the POSIX -// Extended Regular Expression syntax on UNIX-like platforms -// GOOGLETEST_CM0008 DO NOT DELETE -// or a reduced regular exception syntax on other -// platforms, including Windows. -// Logging: -// GTEST_LOG_() - logs messages at the specified severity level. -// LogToStderr() - directs all log messages to stderr. -// FlushInfoLog() - flushes informational log messages. -// -// Stdout and stderr capturing: -// CaptureStdout() - starts capturing stdout. -// GetCapturedStdout() - stops capturing stdout and returns the captured -// string. -// CaptureStderr() - starts capturing stderr. -// GetCapturedStderr() - stops capturing stderr and returns the captured -// string. -// -// Integer types: -// TypeWithSize - maps an integer to a int type. -// Int32, UInt32, Int64, UInt64, TimeInMillis -// - integers of known sizes. -// BiggestInt - the biggest signed integer type. -// -// Command-line utilities: -// GTEST_DECLARE_*() - declares a flag. -// GTEST_DEFINE_*() - defines a flag. -// GetInjectableArgvs() - returns the command line as a vector of strings. -// -// Environment variable utilities: -// GetEnv() - gets the value of an environment variable. -// BoolFromGTestEnv() - parses a bool environment variable. -// Int32FromGTestEnv() - parses an Int32 environment variable. -// StringFromGTestEnv() - parses a string environment variable. -// -// Deprecation warnings: -// GTEST_INTERNAL_DEPRECATED(message) - attribute marking a function as -// deprecated; calling a marked function -// should generate a compiler warning - -#include // for isspace, etc -#include // for ptrdiff_t -#include -#include -#include -#include -#include - -#ifndef _WIN32_WCE -# include -# include -#endif // !_WIN32_WCE - -#if defined __APPLE__ -# include -# include -#endif - -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include -#include -#include // NOLINT - -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file defines the GTEST_OS_* macro. -// It is separate from gtest-port.h so that custom/gtest-port.h can include it. - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ - -// Determines the platform on which Google Test is compiled. -#ifdef __CYGWIN__ -# define GTEST_OS_CYGWIN 1 -# elif defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__) -# define GTEST_OS_WINDOWS_MINGW 1 -# define GTEST_OS_WINDOWS 1 -#elif defined _WIN32 -# define GTEST_OS_WINDOWS 1 -# ifdef _WIN32_WCE -# define GTEST_OS_WINDOWS_MOBILE 1 -# elif defined(WINAPI_FAMILY) -# include -# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -# define GTEST_OS_WINDOWS_DESKTOP 1 -# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) -# define GTEST_OS_WINDOWS_PHONE 1 -# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) -# define GTEST_OS_WINDOWS_RT 1 -# elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE) -# define GTEST_OS_WINDOWS_PHONE 1 -# define GTEST_OS_WINDOWS_TV_TITLE 1 -# else - // WINAPI_FAMILY defined but no known partition matched. - // Default to desktop. -# define GTEST_OS_WINDOWS_DESKTOP 1 -# endif -# else -# define GTEST_OS_WINDOWS_DESKTOP 1 -# endif // _WIN32_WCE -#elif defined __OS2__ -# define GTEST_OS_OS2 1 -#elif defined __APPLE__ -# define GTEST_OS_MAC 1 -# if TARGET_OS_IPHONE -# define GTEST_OS_IOS 1 -# endif -#elif defined __DragonFly__ -# define GTEST_OS_DRAGONFLY 1 -#elif defined __FreeBSD__ -# define GTEST_OS_FREEBSD 1 -#elif defined __Fuchsia__ -# define GTEST_OS_FUCHSIA 1 -#elif defined(__GLIBC__) && defined(__FreeBSD_kernel__) -# define GTEST_OS_GNU_KFREEBSD 1 -#elif defined __linux__ -# define GTEST_OS_LINUX 1 -# if defined __ANDROID__ -# define GTEST_OS_LINUX_ANDROID 1 -# endif -#elif defined __MVS__ -# define GTEST_OS_ZOS 1 -#elif defined(__sun) && defined(__SVR4) -# define GTEST_OS_SOLARIS 1 -#elif defined(_AIX) -# define GTEST_OS_AIX 1 -#elif defined(__hpux) -# define GTEST_OS_HPUX 1 -#elif defined __native_client__ -# define GTEST_OS_NACL 1 -#elif defined __NetBSD__ -# define GTEST_OS_NETBSD 1 -#elif defined __OpenBSD__ -# define GTEST_OS_OPENBSD 1 -#elif defined __QNX__ -# define GTEST_OS_QNX 1 -#elif defined(__HAIKU__) -#define GTEST_OS_HAIKU 1 -#endif // __CYGWIN__ - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_ -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Injection point for custom user configurations. See README for details -// -// ** Custom implementation starts here ** - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_ - -#if !defined(GTEST_DEV_EMAIL_) -# define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" -# define GTEST_FLAG_PREFIX_ "gtest_" -# define GTEST_FLAG_PREFIX_DASH_ "gtest-" -# define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" -# define GTEST_NAME_ "Google Test" -# define GTEST_PROJECT_URL_ "https://github.com/google/googletest/" -#endif // !defined(GTEST_DEV_EMAIL_) - -#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_) -# define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest" -#endif // !defined(GTEST_INIT_GOOGLE_TEST_NAME_) - -// Determines the version of gcc that is used to compile this. -#ifdef __GNUC__ -// 40302 means version 4.3.2. -# define GTEST_GCC_VER_ \ - (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) -#endif // __GNUC__ - -// Macros for disabling Microsoft Visual C++ warnings. -// -// GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385) -// /* code that triggers warnings C4800 and C4385 */ -// GTEST_DISABLE_MSC_WARNINGS_POP_() -#if defined(_MSC_VER) -# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \ - __pragma(warning(push)) \ - __pragma(warning(disable: warnings)) -# define GTEST_DISABLE_MSC_WARNINGS_POP_() \ - __pragma(warning(pop)) -#else -// Not all compilers are MSVC -# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) -# define GTEST_DISABLE_MSC_WARNINGS_POP_() -#endif - -// Clang on Windows does not understand MSVC's pragma warning. -// We need clang-specific way to disable function deprecation warning. -#ifdef __clang__ -# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ - _Pragma("clang diagnostic ignored \"-Wdeprecated-implementations\"") -#define GTEST_DISABLE_MSC_DEPRECATED_POP_() \ - _Pragma("clang diagnostic pop") -#else -# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \ - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996) -# define GTEST_DISABLE_MSC_DEPRECATED_POP_() \ - GTEST_DISABLE_MSC_WARNINGS_POP_() -#endif - -// Brings in definitions for functions used in the testing::internal::posix -// namespace (read, write, close, chdir, isatty, stat). We do not currently -// use them on Windows Mobile. -#if GTEST_OS_WINDOWS -# if !GTEST_OS_WINDOWS_MOBILE -# include -# include -# endif -// In order to avoid having to include , use forward declaration -#if GTEST_OS_WINDOWS_MINGW && !defined(__MINGW64_VERSION_MAJOR) -// MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two -// separate (equivalent) structs, instead of using typedef -typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION; -#else -// Assume CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION. -// This assumption is verified by -// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION. -typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION; -#endif -#else -// This assumes that non-Windows OSes provide unistd.h. For OSes where this -// is not the case, we need to include headers that provide the functions -// mentioned above. -# include -# include -#endif // GTEST_OS_WINDOWS - -#if GTEST_OS_LINUX_ANDROID -// Used to define __ANDROID_API__ matching the target NDK API level. -# include // NOLINT -#endif - -// Defines this to true iff Google Test can use POSIX regular expressions. -#ifndef GTEST_HAS_POSIX_RE -# if GTEST_OS_LINUX_ANDROID -// On Android, is only available starting with Gingerbread. -# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9) -# else -# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS) -# endif -#endif - -#if GTEST_USES_PCRE -// The appropriate headers have already been included. - -#elif GTEST_HAS_POSIX_RE - -// On some platforms, needs someone to define size_t, and -// won't compile otherwise. We can #include it here as we already -// included , which is guaranteed to define size_t through -// . -# include // NOLINT - -# define GTEST_USES_POSIX_RE 1 - -#elif GTEST_OS_WINDOWS - -// is not available on Windows. Use our own simple regex -// implementation instead. -# define GTEST_USES_SIMPLE_RE 1 - -#else - -// may not be available on this platform. Use our own -// simple regex implementation instead. -# define GTEST_USES_SIMPLE_RE 1 - -#endif // GTEST_USES_PCRE - -#ifndef GTEST_HAS_EXCEPTIONS -// The user didn't tell us whether exceptions are enabled, so we need -// to figure it out. -# if defined(_MSC_VER) && defined(_CPPUNWIND) -// MSVC defines _CPPUNWIND to 1 iff exceptions are enabled. -# define GTEST_HAS_EXCEPTIONS 1 -# elif defined(__BORLANDC__) -// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS -// macro to enable exceptions, so we'll do the same. -// Assumes that exceptions are enabled by default. -# ifndef _HAS_EXCEPTIONS -# define _HAS_EXCEPTIONS 1 -# endif // _HAS_EXCEPTIONS -# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS -# elif defined(__clang__) -// clang defines __EXCEPTIONS iff exceptions are enabled before clang 220714, -// but iff cleanups are enabled after that. In Obj-C++ files, there can be -// cleanups for ObjC exceptions which also need cleanups, even if C++ exceptions -// are disabled. clang has __has_feature(cxx_exceptions) which checks for C++ -// exceptions starting at clang r206352, but which checked for cleanups prior to -// that. To reliably check for C++ exception availability with clang, check for -// __EXCEPTIONS && __has_feature(cxx_exceptions). -# define GTEST_HAS_EXCEPTIONS (__EXCEPTIONS && __has_feature(cxx_exceptions)) -# elif defined(__GNUC__) && __EXCEPTIONS -// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. -# define GTEST_HAS_EXCEPTIONS 1 -# elif defined(__SUNPRO_CC) -// Sun Pro CC supports exceptions. However, there is no compile-time way of -// detecting whether they are enabled or not. Therefore, we assume that -// they are enabled unless the user tells us otherwise. -# define GTEST_HAS_EXCEPTIONS 1 -# elif defined(__IBMCPP__) && __EXCEPTIONS -// xlC defines __EXCEPTIONS to 1 iff exceptions are enabled. -# define GTEST_HAS_EXCEPTIONS 1 -# elif defined(__HP_aCC) -// Exception handling is in effect by default in HP aCC compiler. It has to -// be turned of by +noeh compiler option if desired. -# define GTEST_HAS_EXCEPTIONS 1 -# else -// For other compilers, we assume exceptions are disabled to be -// conservative. -# define GTEST_HAS_EXCEPTIONS 0 -# endif // defined(_MSC_VER) || defined(__BORLANDC__) -#endif // GTEST_HAS_EXCEPTIONS - -#if !defined(GTEST_HAS_STD_STRING) -// Even though we don't use this macro any longer, we keep it in case -// some clients still depend on it. -# define GTEST_HAS_STD_STRING 1 -#elif !GTEST_HAS_STD_STRING -// The user told us that ::std::string isn't available. -# error "::std::string isn't available." -#endif // !defined(GTEST_HAS_STD_STRING) - -#ifndef GTEST_HAS_STD_WSTRING -// The user didn't tell us whether ::std::wstring is available, so we need -// to figure it out. -// Cygwin 1.7 and below doesn't support ::std::wstring. -// Solaris' libc++ doesn't support it either. Android has -// no support for it at least as recent as Froyo (2.2). -#define GTEST_HAS_STD_WSTRING \ - (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ - GTEST_OS_HAIKU)) - -#endif // GTEST_HAS_STD_WSTRING - -// Determines whether RTTI is available. -#ifndef GTEST_HAS_RTTI -// The user didn't tell us whether RTTI is enabled, so we need to -// figure it out. - -# ifdef _MSC_VER - -# ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. -# define GTEST_HAS_RTTI 1 -# else -# define GTEST_HAS_RTTI 0 -# endif - -// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. -# elif defined(__GNUC__) - -# ifdef __GXX_RTTI -// When building against STLport with the Android NDK and with -// -frtti -fno-exceptions, the build fails at link time with undefined -// references to __cxa_bad_typeid. Note sure if STL or toolchain bug, -// so disable RTTI when detected. -# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) && \ - !defined(__EXCEPTIONS) -# define GTEST_HAS_RTTI 0 -# else -# define GTEST_HAS_RTTI 1 -# endif // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS -# else -# define GTEST_HAS_RTTI 0 -# endif // __GXX_RTTI - -// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends -// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the -// first version with C++ support. -# elif defined(__clang__) - -# define GTEST_HAS_RTTI __has_feature(cxx_rtti) - -// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if -// both the typeid and dynamic_cast features are present. -# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) - -# ifdef __RTTI_ALL__ -# define GTEST_HAS_RTTI 1 -# else -# define GTEST_HAS_RTTI 0 -# endif - -# else - -// For all other compilers, we assume RTTI is enabled. -# define GTEST_HAS_RTTI 1 - -# endif // _MSC_VER - -#endif // GTEST_HAS_RTTI - -// It's this header's responsibility to #include when RTTI -// is enabled. -#if GTEST_HAS_RTTI -# include -#endif - -// Determines whether Google Test can use the pthreads library. -#ifndef GTEST_HAS_PTHREAD -// The user didn't tell us explicitly, so we make reasonable assumptions about -// which platforms have pthreads support. -// -// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 -// to your compiler flags. -#define GTEST_HAS_PTHREAD \ - (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX || GTEST_OS_QNX || \ - GTEST_OS_FREEBSD || GTEST_OS_NACL || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA || \ - GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_OPENBSD || \ - GTEST_OS_HAIKU) -#endif // GTEST_HAS_PTHREAD - -#if GTEST_HAS_PTHREAD -// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is -// true. -# include // NOLINT - -// For timespec and nanosleep, used below. -# include // NOLINT -#endif - -// Determines whether clone(2) is supported. -// Usually it will only be available on Linux, excluding -// Linux on the Itanium architecture. -// Also see http://linux.die.net/man/2/clone. -#ifndef GTEST_HAS_CLONE -// The user didn't tell us, so we need to figure it out. - -# if GTEST_OS_LINUX && !defined(__ia64__) -# if GTEST_OS_LINUX_ANDROID -// On Android, clone() became available at different API levels for each 32-bit -// architecture. -# if defined(__LP64__) || \ - (defined(__arm__) && __ANDROID_API__ >= 9) || \ - (defined(__mips__) && __ANDROID_API__ >= 12) || \ - (defined(__i386__) && __ANDROID_API__ >= 17) -# define GTEST_HAS_CLONE 1 -# else -# define GTEST_HAS_CLONE 0 -# endif -# else -# define GTEST_HAS_CLONE 1 -# endif -# else -# define GTEST_HAS_CLONE 0 -# endif // GTEST_OS_LINUX && !defined(__ia64__) - -#endif // GTEST_HAS_CLONE - -// Determines whether to support stream redirection. This is used to test -// output correctness and to implement death tests. -#ifndef GTEST_HAS_STREAM_REDIRECTION -// By default, we assume that stream redirection is supported on all -// platforms except known mobile ones. -# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT -# define GTEST_HAS_STREAM_REDIRECTION 0 -# else -# define GTEST_HAS_STREAM_REDIRECTION 1 -# endif // !GTEST_OS_WINDOWS_MOBILE -#endif // GTEST_HAS_STREAM_REDIRECTION - -// Determines whether to support death tests. -// pops up a dialog window that cannot be suppressed programmatically. -#if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ - (GTEST_OS_MAC && !GTEST_OS_IOS) || \ - (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER) || GTEST_OS_WINDOWS_MINGW || \ - GTEST_OS_AIX || GTEST_OS_HPUX || GTEST_OS_OPENBSD || GTEST_OS_QNX || \ - GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA || \ - GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_HAIKU) -# define GTEST_HAS_DEATH_TEST 1 -#endif - -// Determines whether to support type-driven tests. - -// Typed tests need and variadic macros, which GCC, VC++ 8.0, -// Sun Pro CC, IBM Visual Age, and HP aCC support. -#if defined(__GNUC__) || defined(_MSC_VER) || defined(__SUNPRO_CC) || \ - defined(__IBMCPP__) || defined(__HP_aCC) -# define GTEST_HAS_TYPED_TEST 1 -# define GTEST_HAS_TYPED_TEST_P 1 -#endif - -// Determines whether the system compiler uses UTF-16 for encoding wide strings. -#define GTEST_WIDE_STRING_USES_UTF16_ \ - (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_AIX || GTEST_OS_OS2) - -// Determines whether test results can be streamed to a socket. -#if GTEST_OS_LINUX || GTEST_OS_GNU_KFREEBSD || GTEST_OS_DRAGONFLY || \ - GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_OPENBSD -# define GTEST_CAN_STREAM_RESULTS_ 1 -#endif - -// Defines some utility macros. - -// The GNU compiler emits a warning if nested "if" statements are followed by -// an "else" statement and braces are not used to explicitly disambiguate the -// "else" binding. This leads to problems with code like: -// -// if (gate) -// ASSERT_*(condition) << "Some message"; -// -// The "switch (0) case 0:" idiom is used to suppress this. -#ifdef __INTEL_COMPILER -# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ -#else -# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT -#endif - -// Use this annotation at the end of a struct/class definition to -// prevent the compiler from optimizing away instances that are never -// used. This is useful when all interesting logic happens inside the -// c'tor and / or d'tor. Example: -// -// struct Foo { -// Foo() { ... } -// } GTEST_ATTRIBUTE_UNUSED_; -// -// Also use it after a variable or parameter declaration to tell the -// compiler the variable/parameter does not have to be used. -#if defined(__GNUC__) && !defined(COMPILER_ICC) -# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) -#elif defined(__clang__) -# if __has_attribute(unused) -# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) -# endif -#endif -#ifndef GTEST_ATTRIBUTE_UNUSED_ -# define GTEST_ATTRIBUTE_UNUSED_ -#endif - -// Use this annotation before a function that takes a printf format string. -#if (defined(__GNUC__) || defined(__clang__)) && !defined(COMPILER_ICC) -# if defined(__MINGW_PRINTF_FORMAT) -// MinGW has two different printf implementations. Ensure the format macro -// matches the selected implementation. See -// https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/. -# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ - __attribute__((__format__(__MINGW_PRINTF_FORMAT, string_index, \ - first_to_check))) -# else -# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \ - __attribute__((__format__(__printf__, string_index, first_to_check))) -# endif -#else -# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) -#endif - - -// A macro to disallow operator= -// This should be used in the private: declarations for a class. -#define GTEST_DISALLOW_ASSIGN_(type) \ - void operator=(type const &) = delete - -// A macro to disallow copy constructor and operator= -// This should be used in the private: declarations for a class. -#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type) \ - type(type const &) = delete; \ - GTEST_DISALLOW_ASSIGN_(type) - -// Tell the compiler to warn about unused return values for functions declared -// with this macro. The macro should be used on function declarations -// following the argument list: -// -// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; -#if defined(__GNUC__) && !defined(COMPILER_ICC) -# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) -#else -# define GTEST_MUST_USE_RESULT_ -#endif // __GNUC__ && !COMPILER_ICC - -// MS C++ compiler emits warning when a conditional expression is compile time -// constant. In some contexts this warning is false positive and needs to be -// suppressed. Use the following two macros in such cases: -// -// GTEST_INTENTIONAL_CONST_COND_PUSH_() -// while (true) { -// GTEST_INTENTIONAL_CONST_COND_POP_() -// } -# define GTEST_INTENTIONAL_CONST_COND_PUSH_() \ - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127) -# define GTEST_INTENTIONAL_CONST_COND_POP_() \ - GTEST_DISABLE_MSC_WARNINGS_POP_() - -// Determine whether the compiler supports Microsoft's Structured Exception -// Handling. This is supported by several Windows compilers but generally -// does not exist on any other system. -#ifndef GTEST_HAS_SEH -// The user didn't tell us, so we need to figure it out. - -# if defined(_MSC_VER) || defined(__BORLANDC__) -// These two compilers are known to support SEH. -# define GTEST_HAS_SEH 1 -# else -// Assume no SEH. -# define GTEST_HAS_SEH 0 -# endif - -#endif // GTEST_HAS_SEH - -#ifndef GTEST_IS_THREADSAFE - -#define GTEST_IS_THREADSAFE \ - (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ || \ - (GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT) || \ - GTEST_HAS_PTHREAD) - -#endif // GTEST_IS_THREADSAFE - -// GTEST_API_ qualifies all symbols that must be exported. The definitions below -// are guarded by #ifndef to give embedders a chance to define GTEST_API_ in -// gtest/internal/custom/gtest-port.h -#ifndef GTEST_API_ - -#ifdef _MSC_VER -# if GTEST_LINKED_AS_SHARED_LIBRARY -# define GTEST_API_ __declspec(dllimport) -# elif GTEST_CREATE_SHARED_LIBRARY -# define GTEST_API_ __declspec(dllexport) -# endif -#elif __GNUC__ >= 4 || defined(__clang__) -# define GTEST_API_ __attribute__((visibility ("default"))) -#endif // _MSC_VER - -#endif // GTEST_API_ - -#ifndef GTEST_API_ -# define GTEST_API_ -#endif // GTEST_API_ - -#ifndef GTEST_DEFAULT_DEATH_TEST_STYLE -# define GTEST_DEFAULT_DEATH_TEST_STYLE "fast" -#endif // GTEST_DEFAULT_DEATH_TEST_STYLE - -#ifdef __GNUC__ -// Ask the compiler to never inline a given function. -# define GTEST_NO_INLINE_ __attribute__((noinline)) -#else -# define GTEST_NO_INLINE_ -#endif - -// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project. -#if !defined(GTEST_HAS_CXXABI_H_) -# if defined(__GLIBCXX__) || (defined(_LIBCPP_VERSION) && !defined(_MSC_VER)) -# define GTEST_HAS_CXXABI_H_ 1 -# else -# define GTEST_HAS_CXXABI_H_ 0 -# endif -#endif - -// A function level attribute to disable checking for use of uninitialized -// memory when built with MemorySanitizer. -#if defined(__clang__) -# if __has_feature(memory_sanitizer) -# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ \ - __attribute__((no_sanitize_memory)) -# else -# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ -# endif // __has_feature(memory_sanitizer) -#else -# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ -#endif // __clang__ - -// A function level attribute to disable AddressSanitizer instrumentation. -#if defined(__clang__) -# if __has_feature(address_sanitizer) -# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \ - __attribute__((no_sanitize_address)) -# else -# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -# endif // __has_feature(address_sanitizer) -#else -# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ -#endif // __clang__ - -// A function level attribute to disable HWAddressSanitizer instrumentation. -#if defined(__clang__) -# if __has_feature(hwaddress_sanitizer) -# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ \ - __attribute__((no_sanitize("hwaddress"))) -# else -# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -# endif // __has_feature(hwaddress_sanitizer) -#else -# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ -#endif // __clang__ - -// A function level attribute to disable ThreadSanitizer instrumentation. -#if defined(__clang__) -# if __has_feature(thread_sanitizer) -# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ \ - __attribute__((no_sanitize_thread)) -# else -# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ -# endif // __has_feature(thread_sanitizer) -#else -# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ -#endif // __clang__ - -namespace testing { - -class Message; - -// Legacy imports for backwards compatibility. -// New code should use std:: names directly. -using std::get; -using std::make_tuple; -using std::tuple; -using std::tuple_element; -using std::tuple_size; - -namespace internal { - -// A secret type that Google Test users don't know about. It has no -// definition on purpose. Therefore it's impossible to create a -// Secret object, which is what we want. -class Secret; - -// The GTEST_COMPILE_ASSERT_ is a legacy macro used to verify that a compile -// time expression is true (in new code, use static_assert instead). For -// example, you could use it to verify the size of a static array: -// -// GTEST_COMPILE_ASSERT_(GTEST_ARRAY_SIZE_(names) == NUM_NAMES, -// names_incorrect_size); -// -// The second argument to the macro must be a valid C++ identifier. If the -// expression is false, compiler will issue an error containing this identifier. -#define GTEST_COMPILE_ASSERT_(expr, msg) static_assert(expr, #msg) - -// StaticAssertTypeEqHelper is used by StaticAssertTypeEq defined in gtest.h. -// -// This template is declared, but intentionally undefined. -template -struct StaticAssertTypeEqHelper; - -template -struct StaticAssertTypeEqHelper { - enum { value = true }; -}; - -// Same as std::is_same<>. -template -struct IsSame { - enum { value = false }; -}; -template -struct IsSame { - enum { value = true }; -}; - -// Evaluates to the number of elements in 'array'. -#define GTEST_ARRAY_SIZE_(array) (sizeof(array) / sizeof(array[0])) - -// A helper for suppressing warnings on constant condition. It just -// returns 'condition'. -GTEST_API_ bool IsTrue(bool condition); - -// Defines RE. - -#if GTEST_USES_PCRE -// if used, PCRE is injected by custom/gtest-port.h -#elif GTEST_USES_POSIX_RE || GTEST_USES_SIMPLE_RE - -// A simple C++ wrapper for . It uses the POSIX Extended -// Regular Expression syntax. -class GTEST_API_ RE { - public: - // A copy constructor is required by the Standard to initialize object - // references from r-values. - RE(const RE& other) { Init(other.pattern()); } - - // Constructs an RE from a string. - RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT - - RE(const char* regex) { Init(regex); } // NOLINT - ~RE(); - - // Returns the string representation of the regex. - const char* pattern() const { return pattern_; } - - // FullMatch(str, re) returns true iff regular expression re matches - // the entire str. - // PartialMatch(str, re) returns true iff regular expression re - // matches a substring of str (including str itself). - static bool FullMatch(const ::std::string& str, const RE& re) { - return FullMatch(str.c_str(), re); - } - static bool PartialMatch(const ::std::string& str, const RE& re) { - return PartialMatch(str.c_str(), re); - } - - static bool FullMatch(const char* str, const RE& re); - static bool PartialMatch(const char* str, const RE& re); - - private: - void Init(const char* regex); - const char* pattern_; - bool is_valid_; - -# if GTEST_USES_POSIX_RE - - regex_t full_regex_; // For FullMatch(). - regex_t partial_regex_; // For PartialMatch(). - -# else // GTEST_USES_SIMPLE_RE - - const char* full_pattern_; // For FullMatch(); - -# endif - - GTEST_DISALLOW_ASSIGN_(RE); -}; - -#endif // GTEST_USES_PCRE - -// Formats a source file path and a line number as they would appear -// in an error message from the compiler used to compile this code. -GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); - -// Formats a file location for compiler-independent XML output. -// Although this function is not platform dependent, we put it next to -// FormatFileLocation in order to contrast the two functions. -GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, - int line); - -// Defines logging utilities: -// GTEST_LOG_(severity) - logs messages at the specified severity level. The -// message itself is streamed into the macro. -// LogToStderr() - directs all log messages to stderr. -// FlushInfoLog() - flushes informational log messages. - -enum GTestLogSeverity { - GTEST_INFO, - GTEST_WARNING, - GTEST_ERROR, - GTEST_FATAL -}; - -// Formats log entry severity, provides a stream object for streaming the -// log message, and terminates the message with a newline when going out of -// scope. -class GTEST_API_ GTestLog { - public: - GTestLog(GTestLogSeverity severity, const char* file, int line); - - // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. - ~GTestLog(); - - ::std::ostream& GetStream() { return ::std::cerr; } - - private: - const GTestLogSeverity severity_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); -}; - -#if !defined(GTEST_LOG_) - -# define GTEST_LOG_(severity) \ - ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ - __FILE__, __LINE__).GetStream() - -inline void LogToStderr() {} -inline void FlushInfoLog() { fflush(nullptr); } - -#endif // !defined(GTEST_LOG_) - -#if !defined(GTEST_CHECK_) -// INTERNAL IMPLEMENTATION - DO NOT USE. -// -// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition -// is not satisfied. -// Synopsys: -// GTEST_CHECK_(boolean_condition); -// or -// GTEST_CHECK_(boolean_condition) << "Additional message"; -// -// This checks the condition and if the condition is not satisfied -// it prints message about the condition violation, including the -// condition itself, plus additional message streamed into it, if any, -// and then it aborts the program. It aborts the program irrespective of -// whether it is built in the debug mode or not. -# define GTEST_CHECK_(condition) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::IsTrue(condition)) \ - ; \ - else \ - GTEST_LOG_(FATAL) << "Condition " #condition " failed. " -#endif // !defined(GTEST_CHECK_) - -// An all-mode assert to verify that the given POSIX-style function -// call returns 0 (indicating success). Known limitation: this -// doesn't expand to a balanced 'if' statement, so enclose the macro -// in {} if you need to use it as the only statement in an 'if' -// branch. -#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ - if (const int gtest_error = (posix_call)) \ - GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ - << gtest_error - -// Adds reference to a type if it is not a reference type, -// otherwise leaves it unchanged. This is the same as -// tr1::add_reference, which is not widely available yet. -template -struct AddReference { typedef T& type; }; // NOLINT -template -struct AddReference { typedef T& type; }; // NOLINT - -// A handy wrapper around AddReference that works when the argument T -// depends on template parameters. -#define GTEST_ADD_REFERENCE_(T) \ - typename ::testing::internal::AddReference::type - -// Transforms "T" into "const T&" according to standard reference collapsing -// rules (this is only needed as a backport for C++98 compilers that do not -// support reference collapsing). Specifically, it transforms: -// -// char ==> const char& -// const char ==> const char& -// char& ==> char& -// const char& ==> const char& -// -// Note that the non-const reference will not have "const" added. This is -// standard, and necessary so that "T" can always bind to "const T&". -template -struct ConstRef { typedef const T& type; }; -template -struct ConstRef { typedef T& type; }; - -// The argument T must depend on some template parameters. -#define GTEST_REFERENCE_TO_CONST_(T) \ - typename ::testing::internal::ConstRef::type - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Use ImplicitCast_ as a safe version of static_cast for upcasting in -// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a -// const Foo*). When you use ImplicitCast_, the compiler checks that -// the cast is safe. Such explicit ImplicitCast_s are necessary in -// surprisingly many situations where C++ demands an exact type match -// instead of an argument type convertable to a target type. -// -// The syntax for using ImplicitCast_ is the same as for static_cast: -// -// ImplicitCast_(expr) -// -// ImplicitCast_ would have been part of the C++ standard library, -// but the proposal was submitted too late. It will probably make -// its way into the language in the future. -// -// This relatively ugly name is intentional. It prevents clashes with -// similar functions users may have (e.g., implicit_cast). The internal -// namespace alone is not enough because the function can be found by ADL. -template -inline To ImplicitCast_(To x) { return x; } - -// When you upcast (that is, cast a pointer from type Foo to type -// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts -// always succeed. When you downcast (that is, cast a pointer from -// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because -// how do you know the pointer is really of type SubclassOfFoo? It -// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, -// when you downcast, you should use this macro. In debug mode, we -// use dynamic_cast<> to double-check the downcast is legal (we die -// if it's not). In normal mode, we do the efficient static_cast<> -// instead. Thus, it's important to test in debug mode to make sure -// the cast is legal! -// This is the only place in the code we should use dynamic_cast<>. -// In particular, you SHOULDN'T be using dynamic_cast<> in order to -// do RTTI (eg code like this: -// if (dynamic_cast(foo)) HandleASubclass1Object(foo); -// if (dynamic_cast(foo)) HandleASubclass2Object(foo); -// You should design the code some other way not to need this. -// -// This relatively ugly name is intentional. It prevents clashes with -// similar functions users may have (e.g., down_cast). The internal -// namespace alone is not enough because the function can be found by ADL. -template // use like this: DownCast_(foo); -inline To DownCast_(From* f) { // so we only accept pointers - // Ensures that To is a sub-type of From *. This test is here only - // for compile-time type checking, and has no overhead in an - // optimized build at run-time, as it will be optimized away - // completely. - GTEST_INTENTIONAL_CONST_COND_PUSH_() - if (false) { - GTEST_INTENTIONAL_CONST_COND_POP_() - const To to = nullptr; - ::testing::internal::ImplicitCast_(to); - } - -#if GTEST_HAS_RTTI - // RTTI: debug mode only! - GTEST_CHECK_(f == nullptr || dynamic_cast(f) != nullptr); -#endif - return static_cast(f); -} - -// Downcasts the pointer of type Base to Derived. -// Derived must be a subclass of Base. The parameter MUST -// point to a class of type Derived, not any subclass of it. -// When RTTI is available, the function performs a runtime -// check to enforce this. -template -Derived* CheckedDowncastToActualType(Base* base) { -#if GTEST_HAS_RTTI - GTEST_CHECK_(typeid(*base) == typeid(Derived)); -#endif - -#if GTEST_HAS_DOWNCAST_ - return ::down_cast(base); -#elif GTEST_HAS_RTTI - return dynamic_cast(base); // NOLINT -#else - return static_cast(base); // Poor man's downcast. -#endif -} - -#if GTEST_HAS_STREAM_REDIRECTION - -// Defines the stderr capturer: -// CaptureStdout - starts capturing stdout. -// GetCapturedStdout - stops capturing stdout and returns the captured string. -// CaptureStderr - starts capturing stderr. -// GetCapturedStderr - stops capturing stderr and returns the captured string. -// -GTEST_API_ void CaptureStdout(); -GTEST_API_ std::string GetCapturedStdout(); -GTEST_API_ void CaptureStderr(); -GTEST_API_ std::string GetCapturedStderr(); - -#endif // GTEST_HAS_STREAM_REDIRECTION -// Returns the size (in bytes) of a file. -GTEST_API_ size_t GetFileSize(FILE* file); - -// Reads the entire content of a file as a string. -GTEST_API_ std::string ReadEntireFile(FILE* file); - -// All command line arguments. -GTEST_API_ std::vector GetArgvs(); - -#if GTEST_HAS_DEATH_TEST - -std::vector GetInjectableArgvs(); -// Deprecated: pass the args vector by value instead. -void SetInjectableArgvs(const std::vector* new_argvs); -void SetInjectableArgvs(const std::vector& new_argvs); -void ClearInjectableArgvs(); - -#endif // GTEST_HAS_DEATH_TEST - -// Defines synchronization primitives. -#if GTEST_IS_THREADSAFE -# if GTEST_HAS_PTHREAD -// Sleeps for (roughly) n milliseconds. This function is only for testing -// Google Test's own constructs. Don't use it in user tests, either -// directly or indirectly. -inline void SleepMilliseconds(int n) { - const timespec time = { - 0, // 0 seconds. - n * 1000L * 1000L, // And n ms. - }; - nanosleep(&time, nullptr); -} -# endif // GTEST_HAS_PTHREAD - -# if GTEST_HAS_NOTIFICATION_ -// Notification has already been imported into the namespace. -// Nothing to do here. - -# elif GTEST_HAS_PTHREAD -// Allows a controller thread to pause execution of newly created -// threads until notified. Instances of this class must be created -// and destroyed in the controller thread. -// -// This class is only for testing Google Test's own constructs. Do not -// use it in user tests, either directly or indirectly. -class Notification { - public: - Notification() : notified_(false) { - GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr)); - } - ~Notification() { - pthread_mutex_destroy(&mutex_); - } - - // Notifies all threads created with this notification to start. Must - // be called from the controller thread. - void Notify() { - pthread_mutex_lock(&mutex_); - notified_ = true; - pthread_mutex_unlock(&mutex_); - } - - // Blocks until the controller thread notifies. Must be called from a test - // thread. - void WaitForNotification() { - for (;;) { - pthread_mutex_lock(&mutex_); - const bool notified = notified_; - pthread_mutex_unlock(&mutex_); - if (notified) - break; - SleepMilliseconds(10); - } - } - - private: - pthread_mutex_t mutex_; - bool notified_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); -}; - -# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT - -GTEST_API_ void SleepMilliseconds(int n); - -// Provides leak-safe Windows kernel handle ownership. -// Used in death tests and in threading support. -class GTEST_API_ AutoHandle { - public: - // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to - // avoid including in this header file. Including is - // undesirable because it defines a lot of symbols and macros that tend to - // conflict with client code. This assumption is verified by - // WindowsTypesTest.HANDLEIsVoidStar. - typedef void* Handle; - AutoHandle(); - explicit AutoHandle(Handle handle); - - ~AutoHandle(); - - Handle Get() const; - void Reset(); - void Reset(Handle handle); - - private: - // Returns true iff the handle is a valid handle object that can be closed. - bool IsCloseable() const; - - Handle handle_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); -}; - -// Allows a controller thread to pause execution of newly created -// threads until notified. Instances of this class must be created -// and destroyed in the controller thread. -// -// This class is only for testing Google Test's own constructs. Do not -// use it in user tests, either directly or indirectly. -class GTEST_API_ Notification { - public: - Notification(); - void Notify(); - void WaitForNotification(); - - private: - AutoHandle event_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); -}; -# endif // GTEST_HAS_NOTIFICATION_ - -// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD -// defined, but we don't want to use MinGW's pthreads implementation, which -// has conformance problems with some versions of the POSIX standard. -# if GTEST_HAS_PTHREAD && !GTEST_OS_WINDOWS_MINGW - -// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. -// Consequently, it cannot select a correct instantiation of ThreadWithParam -// in order to call its Run(). Introducing ThreadWithParamBase as a -// non-templated base class for ThreadWithParam allows us to bypass this -// problem. -class ThreadWithParamBase { - public: - virtual ~ThreadWithParamBase() {} - virtual void Run() = 0; -}; - -// pthread_create() accepts a pointer to a function type with the C linkage. -// According to the Standard (7.5/1), function types with different linkages -// are different even if they are otherwise identical. Some compilers (for -// example, SunStudio) treat them as different types. Since class methods -// cannot be defined with C-linkage we need to define a free C-function to -// pass into pthread_create(). -extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { - static_cast(thread)->Run(); - return nullptr; -} - -// Helper class for testing Google Test's multi-threading constructs. -// To use it, write: -// -// void ThreadFunc(int param) { /* Do things with param */ } -// Notification thread_can_start; -// ... -// // The thread_can_start parameter is optional; you can supply NULL. -// ThreadWithParam thread(&ThreadFunc, 5, &thread_can_start); -// thread_can_start.Notify(); -// -// These classes are only for testing Google Test's own constructs. Do -// not use them in user tests, either directly or indirectly. -template -class ThreadWithParam : public ThreadWithParamBase { - public: - typedef void UserThreadFunc(T); - - ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) - : func_(func), - param_(param), - thread_can_start_(thread_can_start), - finished_(false) { - ThreadWithParamBase* const base = this; - // The thread can be created only after all fields except thread_ - // have been initialized. - GTEST_CHECK_POSIX_SUCCESS_( - pthread_create(&thread_, nullptr, &ThreadFuncWithCLinkage, base)); - } - ~ThreadWithParam() override { Join(); } - - void Join() { - if (!finished_) { - GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, nullptr)); - finished_ = true; - } - } - - void Run() override { - if (thread_can_start_ != nullptr) thread_can_start_->WaitForNotification(); - func_(param_); - } - - private: - UserThreadFunc* const func_; // User-supplied thread function. - const T param_; // User-supplied parameter to the thread function. - // When non-NULL, used to block execution until the controller thread - // notifies. - Notification* const thread_can_start_; - bool finished_; // true iff we know that the thread function has finished. - pthread_t thread_; // The native thread object. - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); -}; -# endif // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD || - // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ - -# if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ -// Mutex and ThreadLocal have already been imported into the namespace. -// Nothing to do here. - -# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT - -// Mutex implements mutex on Windows platforms. It is used in conjunction -// with class MutexLock: -// -// Mutex mutex; -// ... -// MutexLock lock(&mutex); // Acquires the mutex and releases it at the -// // end of the current scope. -// -// A static Mutex *must* be defined or declared using one of the following -// macros: -// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); -// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); -// -// (A non-static Mutex is defined/declared in the usual way). -class GTEST_API_ Mutex { - public: - enum MutexType { kStatic = 0, kDynamic = 1 }; - // We rely on kStaticMutex being 0 as it is to what the linker initializes - // type_ in static mutexes. critical_section_ will be initialized lazily - // in ThreadSafeLazyInit(). - enum StaticConstructorSelector { kStaticMutex = 0 }; - - // This constructor intentionally does nothing. It relies on type_ being - // statically initialized to 0 (effectively setting it to kStatic) and on - // ThreadSafeLazyInit() to lazily initialize the rest of the members. - explicit Mutex(StaticConstructorSelector /*dummy*/) {} - - Mutex(); - ~Mutex(); - - void Lock(); - - void Unlock(); - - // Does nothing if the current thread holds the mutex. Otherwise, crashes - // with high probability. - void AssertHeld(); - - private: - // Initializes owner_thread_id_ and critical_section_ in static mutexes. - void ThreadSafeLazyInit(); - - // Per https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503, - // we assume that 0 is an invalid value for thread IDs. - unsigned int owner_thread_id_; - - // For static mutexes, we rely on these members being initialized to zeros - // by the linker. - MutexType type_; - long critical_section_init_phase_; // NOLINT - GTEST_CRITICAL_SECTION* critical_section_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); -}; - -# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ - extern ::testing::internal::Mutex mutex - -# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ - ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex) - -// We cannot name this class MutexLock because the ctor declaration would -// conflict with a macro named MutexLock, which is defined on some -// platforms. That macro is used as a defensive measure to prevent against -// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than -// "MutexLock l(&mu)". Hence the typedef trick below. -class GTestMutexLock { - public: - explicit GTestMutexLock(Mutex* mutex) - : mutex_(mutex) { mutex_->Lock(); } - - ~GTestMutexLock() { mutex_->Unlock(); } - - private: - Mutex* const mutex_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); -}; - -typedef GTestMutexLock MutexLock; - -// Base class for ValueHolder. Allows a caller to hold and delete a value -// without knowing its type. -class ThreadLocalValueHolderBase { - public: - virtual ~ThreadLocalValueHolderBase() {} -}; - -// Provides a way for a thread to send notifications to a ThreadLocal -// regardless of its parameter type. -class ThreadLocalBase { - public: - // Creates a new ValueHolder object holding a default value passed to - // this ThreadLocal's constructor and returns it. It is the caller's - // responsibility not to call this when the ThreadLocal instance already - // has a value on the current thread. - virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0; - - protected: - ThreadLocalBase() {} - virtual ~ThreadLocalBase() {} - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocalBase); -}; - -// Maps a thread to a set of ThreadLocals that have values instantiated on that -// thread and notifies them when the thread exits. A ThreadLocal instance is -// expected to persist until all threads it has values on have terminated. -class GTEST_API_ ThreadLocalRegistry { - public: - // Registers thread_local_instance as having value on the current thread. - // Returns a value that can be used to identify the thread from other threads. - static ThreadLocalValueHolderBase* GetValueOnCurrentThread( - const ThreadLocalBase* thread_local_instance); - - // Invoked when a ThreadLocal instance is destroyed. - static void OnThreadLocalDestroyed( - const ThreadLocalBase* thread_local_instance); -}; - -class GTEST_API_ ThreadWithParamBase { - public: - void Join(); - - protected: - class Runnable { - public: - virtual ~Runnable() {} - virtual void Run() = 0; - }; - - ThreadWithParamBase(Runnable *runnable, Notification* thread_can_start); - virtual ~ThreadWithParamBase(); - - private: - AutoHandle thread_; -}; - -// Helper class for testing Google Test's multi-threading constructs. -template -class ThreadWithParam : public ThreadWithParamBase { - public: - typedef void UserThreadFunc(T); - - ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start) - : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) { - } - virtual ~ThreadWithParam() {} - - private: - class RunnableImpl : public Runnable { - public: - RunnableImpl(UserThreadFunc* func, T param) - : func_(func), - param_(param) { - } - virtual ~RunnableImpl() {} - virtual void Run() { - func_(param_); - } - - private: - UserThreadFunc* const func_; - const T param_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(RunnableImpl); - }; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); -}; - -// Implements thread-local storage on Windows systems. -// -// // Thread 1 -// ThreadLocal tl(100); // 100 is the default value for each thread. -// -// // Thread 2 -// tl.set(150); // Changes the value for thread 2 only. -// EXPECT_EQ(150, tl.get()); -// -// // Thread 1 -// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. -// tl.set(200); -// EXPECT_EQ(200, tl.get()); -// -// The template type argument T must have a public copy constructor. -// In addition, the default ThreadLocal constructor requires T to have -// a public default constructor. -// -// The users of a TheadLocal instance have to make sure that all but one -// threads (including the main one) using that instance have exited before -// destroying it. Otherwise, the per-thread objects managed for them by the -// ThreadLocal instance are not guaranteed to be destroyed on all platforms. -// -// Google Test only uses global ThreadLocal objects. That means they -// will die after main() has returned. Therefore, no per-thread -// object managed by Google Test will be leaked as long as all threads -// using Google Test have exited when main() returns. -template -class ThreadLocal : public ThreadLocalBase { - public: - ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {} - explicit ThreadLocal(const T& value) - : default_factory_(new InstanceValueHolderFactory(value)) {} - - ~ThreadLocal() { ThreadLocalRegistry::OnThreadLocalDestroyed(this); } - - T* pointer() { return GetOrCreateValue(); } - const T* pointer() const { return GetOrCreateValue(); } - const T& get() const { return *pointer(); } - void set(const T& value) { *pointer() = value; } - - private: - // Holds a value of T. Can be deleted via its base class without the caller - // knowing the type of T. - class ValueHolder : public ThreadLocalValueHolderBase { - public: - ValueHolder() : value_() {} - explicit ValueHolder(const T& value) : value_(value) {} - - T* pointer() { return &value_; } - - private: - T value_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); - }; - - - T* GetOrCreateValue() const { - return static_cast( - ThreadLocalRegistry::GetValueOnCurrentThread(this))->pointer(); - } - - virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const { - return default_factory_->MakeNewHolder(); - } - - class ValueHolderFactory { - public: - ValueHolderFactory() {} - virtual ~ValueHolderFactory() {} - virtual ValueHolder* MakeNewHolder() const = 0; - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); - }; - - class DefaultValueHolderFactory : public ValueHolderFactory { - public: - DefaultValueHolderFactory() {} - virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); } - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); - }; - - class InstanceValueHolderFactory : public ValueHolderFactory { - public: - explicit InstanceValueHolderFactory(const T& value) : value_(value) {} - virtual ValueHolder* MakeNewHolder() const { - return new ValueHolder(value_); - } - - private: - const T value_; // The value for each thread. - - GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); - }; - - std::unique_ptr default_factory_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); -}; - -# elif GTEST_HAS_PTHREAD - -// MutexBase and Mutex implement mutex on pthreads-based platforms. -class MutexBase { - public: - // Acquires this mutex. - void Lock() { - GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); - owner_ = pthread_self(); - has_owner_ = true; - } - - // Releases this mutex. - void Unlock() { - // Since the lock is being released the owner_ field should no longer be - // considered valid. We don't protect writing to has_owner_ here, as it's - // the caller's responsibility to ensure that the current thread holds the - // mutex when this is called. - has_owner_ = false; - GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); - } - - // Does nothing if the current thread holds the mutex. Otherwise, crashes - // with high probability. - void AssertHeld() const { - GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self())) - << "The current thread is not holding the mutex @" << this; - } - - // A static mutex may be used before main() is entered. It may even - // be used before the dynamic initialization stage. Therefore we - // must be able to initialize a static mutex object at link time. - // This means MutexBase has to be a POD and its member variables - // have to be public. - public: - pthread_mutex_t mutex_; // The underlying pthread mutex. - // has_owner_ indicates whether the owner_ field below contains a valid thread - // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All - // accesses to the owner_ field should be protected by a check of this field. - // An alternative might be to memset() owner_ to all zeros, but there's no - // guarantee that a zero'd pthread_t is necessarily invalid or even different - // from pthread_self(). - bool has_owner_; - pthread_t owner_; // The thread holding the mutex. -}; - -// Forward-declares a static mutex. -# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ - extern ::testing::internal::MutexBase mutex - -// Defines and statically (i.e. at link time) initializes a static mutex. -// The initialization list here does not explicitly initialize each field, -// instead relying on default initialization for the unspecified fields. In -// particular, the owner_ field (a pthread_t) is not explicitly initialized. -// This allows initialization to work whether pthread_t is a scalar or struct. -// The flag -Wmissing-field-initializers must not be specified for this to work. -#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ - ::testing::internal::MutexBase mutex = {PTHREAD_MUTEX_INITIALIZER, false, 0} - -// The Mutex class can only be used for mutexes created at runtime. It -// shares its API with MutexBase otherwise. -class Mutex : public MutexBase { - public: - Mutex() { - GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr)); - has_owner_ = false; - } - ~Mutex() { - GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); - } - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); -}; - -// We cannot name this class MutexLock because the ctor declaration would -// conflict with a macro named MutexLock, which is defined on some -// platforms. That macro is used as a defensive measure to prevent against -// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than -// "MutexLock l(&mu)". Hence the typedef trick below. -class GTestMutexLock { - public: - explicit GTestMutexLock(MutexBase* mutex) - : mutex_(mutex) { mutex_->Lock(); } - - ~GTestMutexLock() { mutex_->Unlock(); } - - private: - MutexBase* const mutex_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); -}; - -typedef GTestMutexLock MutexLock; - -// Helpers for ThreadLocal. - -// pthread_key_create() requires DeleteThreadLocalValue() to have -// C-linkage. Therefore it cannot be templatized to access -// ThreadLocal. Hence the need for class -// ThreadLocalValueHolderBase. -class ThreadLocalValueHolderBase { - public: - virtual ~ThreadLocalValueHolderBase() {} -}; - -// Called by pthread to delete thread-local data stored by -// pthread_setspecific(). -extern "C" inline void DeleteThreadLocalValue(void* value_holder) { - delete static_cast(value_holder); -} - -// Implements thread-local storage on pthreads-based systems. -template -class GTEST_API_ ThreadLocal { - public: - ThreadLocal() - : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {} - explicit ThreadLocal(const T& value) - : key_(CreateKey()), - default_factory_(new InstanceValueHolderFactory(value)) {} - - ~ThreadLocal() { - // Destroys the managed object for the current thread, if any. - DeleteThreadLocalValue(pthread_getspecific(key_)); - - // Releases resources associated with the key. This will *not* - // delete managed objects for other threads. - GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); - } - - T* pointer() { return GetOrCreateValue(); } - const T* pointer() const { return GetOrCreateValue(); } - const T& get() const { return *pointer(); } - void set(const T& value) { *pointer() = value; } - - private: - // Holds a value of type T. - class ValueHolder : public ThreadLocalValueHolderBase { - public: - ValueHolder() : value_() {} - explicit ValueHolder(const T& value) : value_(value) {} - - T* pointer() { return &value_; } - - private: - T value_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); - }; - - static pthread_key_t CreateKey() { - pthread_key_t key; - // When a thread exits, DeleteThreadLocalValue() will be called on - // the object managed for that thread. - GTEST_CHECK_POSIX_SUCCESS_( - pthread_key_create(&key, &DeleteThreadLocalValue)); - return key; - } - - T* GetOrCreateValue() const { - ThreadLocalValueHolderBase* const holder = - static_cast(pthread_getspecific(key_)); - if (holder != nullptr) { - return CheckedDowncastToActualType(holder)->pointer(); - } - - ValueHolder* const new_holder = default_factory_->MakeNewHolder(); - ThreadLocalValueHolderBase* const holder_base = new_holder; - GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); - return new_holder->pointer(); - } - - class ValueHolderFactory { - public: - ValueHolderFactory() {} - virtual ~ValueHolderFactory() {} - virtual ValueHolder* MakeNewHolder() const = 0; - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory); - }; - - class DefaultValueHolderFactory : public ValueHolderFactory { - public: - DefaultValueHolderFactory() {} - virtual ValueHolder* MakeNewHolder() const { return new ValueHolder(); } - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory); - }; - - class InstanceValueHolderFactory : public ValueHolderFactory { - public: - explicit InstanceValueHolderFactory(const T& value) : value_(value) {} - virtual ValueHolder* MakeNewHolder() const { - return new ValueHolder(value_); - } - - private: - const T value_; // The value for each thread. - - GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory); - }; - - // A key pthreads uses for looking up per-thread values. - const pthread_key_t key_; - std::unique_ptr default_factory_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); -}; - -# endif // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ - -#else // GTEST_IS_THREADSAFE - -// A dummy implementation of synchronization primitives (mutex, lock, -// and thread-local variable). Necessary for compiling Google Test where -// mutex is not supported - using Google Test in multiple threads is not -// supported on such platforms. - -class Mutex { - public: - Mutex() {} - void Lock() {} - void Unlock() {} - void AssertHeld() const {} -}; - -# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ - extern ::testing::internal::Mutex mutex - -# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex - -// We cannot name this class MutexLock because the ctor declaration would -// conflict with a macro named MutexLock, which is defined on some -// platforms. That macro is used as a defensive measure to prevent against -// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than -// "MutexLock l(&mu)". Hence the typedef trick below. -class GTestMutexLock { - public: - explicit GTestMutexLock(Mutex*) {} // NOLINT -}; - -typedef GTestMutexLock MutexLock; - -template -class GTEST_API_ ThreadLocal { - public: - ThreadLocal() : value_() {} - explicit ThreadLocal(const T& value) : value_(value) {} - T* pointer() { return &value_; } - const T* pointer() const { return &value_; } - const T& get() const { return value_; } - void set(const T& value) { value_ = value; } - private: - T value_; -}; - -#endif // GTEST_IS_THREADSAFE - -// Returns the number of threads running in the process, or 0 to indicate that -// we cannot detect it. -GTEST_API_ size_t GetThreadCount(); - -template -struct bool_constant { - typedef bool_constant type; - static const bool value = bool_value; -}; -template const bool bool_constant::value; - -typedef bool_constant false_type; -typedef bool_constant true_type; - -template -struct is_same : public false_type {}; - -template -struct is_same : public true_type {}; - -template -struct IteratorTraits { - typedef typename Iterator::value_type value_type; -}; - - -template -struct IteratorTraits { - typedef T value_type; -}; - -template -struct IteratorTraits { - typedef T value_type; -}; - -#if GTEST_OS_WINDOWS -# define GTEST_PATH_SEP_ "\\" -# define GTEST_HAS_ALT_PATH_SEP_ 1 -// The biggest signed integer type the compiler supports. -typedef __int64 BiggestInt; -#else -# define GTEST_PATH_SEP_ "/" -# define GTEST_HAS_ALT_PATH_SEP_ 0 -typedef long long BiggestInt; // NOLINT -#endif // GTEST_OS_WINDOWS - -// Utilities for char. - -// isspace(int ch) and friends accept an unsigned char or EOF. char -// may be signed, depending on the compiler (or compiler flags). -// Therefore we need to cast a char to unsigned char before calling -// isspace(), etc. - -inline bool IsAlpha(char ch) { - return isalpha(static_cast(ch)) != 0; -} -inline bool IsAlNum(char ch) { - return isalnum(static_cast(ch)) != 0; -} -inline bool IsDigit(char ch) { - return isdigit(static_cast(ch)) != 0; -} -inline bool IsLower(char ch) { - return islower(static_cast(ch)) != 0; -} -inline bool IsSpace(char ch) { - return isspace(static_cast(ch)) != 0; -} -inline bool IsUpper(char ch) { - return isupper(static_cast(ch)) != 0; -} -inline bool IsXDigit(char ch) { - return isxdigit(static_cast(ch)) != 0; -} -inline bool IsXDigit(wchar_t ch) { - const unsigned char low_byte = static_cast(ch); - return ch == low_byte && isxdigit(low_byte) != 0; -} - -inline char ToLower(char ch) { - return static_cast(tolower(static_cast(ch))); -} -inline char ToUpper(char ch) { - return static_cast(toupper(static_cast(ch))); -} - -inline std::string StripTrailingSpaces(std::string str) { - std::string::iterator it = str.end(); - while (it != str.begin() && IsSpace(*--it)) - it = str.erase(it); - return str; -} - -// The testing::internal::posix namespace holds wrappers for common -// POSIX functions. These wrappers hide the differences between -// Windows/MSVC and POSIX systems. Since some compilers define these -// standard functions as macros, the wrapper cannot have the same name -// as the wrapped function. - -namespace posix { - -// Functions with a different name on Windows. - -#if GTEST_OS_WINDOWS - -typedef struct _stat StatStruct; - -# ifdef __BORLANDC__ -inline int IsATTY(int fd) { return isatty(fd); } -inline int StrCaseCmp(const char* s1, const char* s2) { - return stricmp(s1, s2); -} -inline char* StrDup(const char* src) { return strdup(src); } -# else // !__BORLANDC__ -# if GTEST_OS_WINDOWS_MOBILE -inline int IsATTY(int /* fd */) { return 0; } -# else -inline int IsATTY(int fd) { return _isatty(fd); } -# endif // GTEST_OS_WINDOWS_MOBILE -inline int StrCaseCmp(const char* s1, const char* s2) { - return _stricmp(s1, s2); -} -inline char* StrDup(const char* src) { return _strdup(src); } -# endif // __BORLANDC__ - -# if GTEST_OS_WINDOWS_MOBILE -inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } -// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this -// time and thus not defined there. -# else -inline int FileNo(FILE* file) { return _fileno(file); } -inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } -inline int RmDir(const char* dir) { return _rmdir(dir); } -inline bool IsDir(const StatStruct& st) { - return (_S_IFDIR & st.st_mode) != 0; -} -# endif // GTEST_OS_WINDOWS_MOBILE - -#else - -typedef struct stat StatStruct; - -inline int FileNo(FILE* file) { return fileno(file); } -inline int IsATTY(int fd) { return isatty(fd); } -inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } -inline int StrCaseCmp(const char* s1, const char* s2) { - return strcasecmp(s1, s2); -} -inline char* StrDup(const char* src) { return strdup(src); } -inline int RmDir(const char* dir) { return rmdir(dir); } -inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } - -#endif // GTEST_OS_WINDOWS - -// Functions deprecated by MSVC 8.0. - -GTEST_DISABLE_MSC_DEPRECATED_PUSH_() - -inline const char* StrNCpy(char* dest, const char* src, size_t n) { - return strncpy(dest, src, n); -} - -// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and -// StrError() aren't needed on Windows CE at this time and thus not -// defined there. - -#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT -inline int ChDir(const char* dir) { return chdir(dir); } -#endif -inline FILE* FOpen(const char* path, const char* mode) { - return fopen(path, mode); -} -#if !GTEST_OS_WINDOWS_MOBILE -inline FILE *FReopen(const char* path, const char* mode, FILE* stream) { - return freopen(path, mode, stream); -} -inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } -#endif -inline int FClose(FILE* fp) { return fclose(fp); } -#if !GTEST_OS_WINDOWS_MOBILE -inline int Read(int fd, void* buf, unsigned int count) { - return static_cast(read(fd, buf, count)); -} -inline int Write(int fd, const void* buf, unsigned int count) { - return static_cast(write(fd, buf, count)); -} -inline int Close(int fd) { return close(fd); } -inline const char* StrError(int errnum) { return strerror(errnum); } -#endif -inline const char* GetEnv(const char* name) { -#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT - // We are on Windows CE, which has no environment variables. - static_cast(name); // To prevent 'unused argument' warning. - return nullptr; -#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) - // Environment variables which we programmatically clear will be set to the - // empty string rather than unset (NULL). Handle that case. - const char* const env = getenv(name); - return (env != nullptr && env[0] != '\0') ? env : nullptr; -#else - return getenv(name); -#endif -} - -GTEST_DISABLE_MSC_DEPRECATED_POP_() - -#if GTEST_OS_WINDOWS_MOBILE -// Windows CE has no C library. The abort() function is used in -// several places in Google Test. This implementation provides a reasonable -// imitation of standard behaviour. -[[noreturn]] void Abort(); -#else -[[noreturn]] inline void Abort() { abort(); } -#endif // GTEST_OS_WINDOWS_MOBILE - -} // namespace posix - -// MSVC "deprecates" snprintf and issues warnings wherever it is used. In -// order to avoid these warnings, we need to use _snprintf or _snprintf_s on -// MSVC-based platforms. We map the GTEST_SNPRINTF_ macro to the appropriate -// function in order to achieve that. We use macro definition here because -// snprintf is a variadic function. -#if _MSC_VER && !GTEST_OS_WINDOWS_MOBILE -// MSVC 2005 and above support variadic macros. -# define GTEST_SNPRINTF_(buffer, size, format, ...) \ - _snprintf_s(buffer, size, size, format, __VA_ARGS__) -#elif defined(_MSC_VER) -// Windows CE does not define _snprintf_s -# define GTEST_SNPRINTF_ _snprintf -#else -# define GTEST_SNPRINTF_ snprintf -#endif - -// The maximum number a BiggestInt can represent. This definition -// works no matter BiggestInt is represented in one's complement or -// two's complement. -// -// We cannot rely on numeric_limits in STL, as __int64 and long long -// are not part of standard C++ and numeric_limits doesn't need to be -// defined for them. -const BiggestInt kMaxBiggestInt = - ~(static_cast(1) << (8*sizeof(BiggestInt) - 1)); - -// This template class serves as a compile-time function from size to -// type. It maps a size in bytes to a primitive type with that -// size. e.g. -// -// TypeWithSize<4>::UInt -// -// is typedef-ed to be unsigned int (unsigned integer made up of 4 -// bytes). -// -// Such functionality should belong to STL, but I cannot find it -// there. -// -// Google Test uses this class in the implementation of floating-point -// comparison. -// -// For now it only handles UInt (unsigned int) as that's all Google Test -// needs. Other types can be easily added in the future if need -// arises. -template -class TypeWithSize { - public: - // This prevents the user from using TypeWithSize with incorrect - // values of N. - typedef void UInt; -}; - -// The specialization for size 4. -template <> -class TypeWithSize<4> { - public: - // unsigned int has size 4 in both gcc and MSVC. - // - // As base/basictypes.h doesn't compile on Windows, we cannot use - // uint32, uint64, and etc here. - typedef int Int; - typedef unsigned int UInt; -}; - -// The specialization for size 8. -template <> -class TypeWithSize<8> { - public: -#if GTEST_OS_WINDOWS - typedef __int64 Int; - typedef unsigned __int64 UInt; -#else - typedef long long Int; // NOLINT - typedef unsigned long long UInt; // NOLINT -#endif // GTEST_OS_WINDOWS -}; - -// Integer types of known sizes. -typedef TypeWithSize<4>::Int Int32; -typedef TypeWithSize<4>::UInt UInt32; -typedef TypeWithSize<8>::Int Int64; -typedef TypeWithSize<8>::UInt UInt64; -typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. - -// Utilities for command line flags and environment variables. - -// Macro for referencing flags. -#if !defined(GTEST_FLAG) -# define GTEST_FLAG(name) FLAGS_gtest_##name -#endif // !defined(GTEST_FLAG) - -#if !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) -# define GTEST_USE_OWN_FLAGFILE_FLAG_ 1 -#endif // !defined(GTEST_USE_OWN_FLAGFILE_FLAG_) - -#if !defined(GTEST_DECLARE_bool_) -# define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver - -// Macros for declaring flags. -# define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) -# define GTEST_DECLARE_int32_(name) \ - GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name) -# define GTEST_DECLARE_string_(name) \ - GTEST_API_ extern ::std::string GTEST_FLAG(name) - -// Macros for defining flags. -# define GTEST_DEFINE_bool_(name, default_val, doc) \ - GTEST_API_ bool GTEST_FLAG(name) = (default_val) -# define GTEST_DEFINE_int32_(name, default_val, doc) \ - GTEST_API_ ::testing::internal::Int32 GTEST_FLAG(name) = (default_val) -# define GTEST_DEFINE_string_(name, default_val, doc) \ - GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val) - -#endif // !defined(GTEST_DECLARE_bool_) - -// Thread annotations -#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) -# define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) -# define GTEST_LOCK_EXCLUDED_(locks) -#endif // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_) - -// Parses 'str' for a 32-bit signed integer. If successful, writes the result -// to *value and returns true; otherwise leaves *value unchanged and returns -// false. -bool ParseInt32(const Message& src_text, const char* str, Int32* value); - -// Parses a bool/Int32/string from the environment variable -// corresponding to the given Google Test flag. -bool BoolFromGTestEnv(const char* flag, bool default_val); -GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); -std::string OutputFlagAlsoCheckEnvVar(); -const char* StringFromGTestEnv(const char* flag, const char* default_val); - -} // namespace internal -} // namespace testing - -#if !defined(GTEST_INTERNAL_DEPRECATED) - -// Internal Macro to mark an API deprecated, for googletest usage only -// Usage: class GTEST_INTERNAL_DEPRECATED(message) MyClass or -// GTEST_INTERNAL_DEPRECATED(message) myFunction(); Every usage of -// a deprecated entity will trigger a warning when compiled with -// `-Wdeprecated-declarations` option (clang, gcc, any __GNUC__ compiler). -// For msvc /W3 option will need to be used -// Note that for 'other' compilers this macro evaluates to nothing to prevent -// compilations errors. -#if defined(_MSC_VER) -#define GTEST_INTERNAL_DEPRECATED(message) __declspec(deprecated(message)) -#elif defined(__GNUC__) -#define GTEST_INTERNAL_DEPRECATED(message) __attribute__((deprecated(message))) -#else -#define GTEST_INTERNAL_DEPRECATED(message) -#endif - -#endif // !defined(GTEST_INTERNAL_DEPRECATED) - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ - -#if GTEST_OS_LINUX -# include -# include -# include -# include -#endif // GTEST_OS_LINUX - -#if GTEST_HAS_EXCEPTIONS -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file defines the Message class. -// -// IMPORTANT NOTE: Due to limitation of the C++ language, we have to -// leave some internal implementation details in this header file. -// They are clearly marked by comments like this: -// -// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -// -// Such code is NOT meant to be used by a user directly, and is subject -// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user -// program! - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ -#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ - -#include -#include - - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -// Ensures that there is at least one operator<< in the global namespace. -// See Message& operator<<(...) below for why. -void operator<<(const testing::internal::Secret&, int); - -namespace testing { - -// The Message class works like an ostream repeater. -// -// Typical usage: -// -// 1. You stream a bunch of values to a Message object. -// It will remember the text in a stringstream. -// 2. Then you stream the Message object to an ostream. -// This causes the text in the Message to be streamed -// to the ostream. -// -// For example; -// -// testing::Message foo; -// foo << 1 << " != " << 2; -// std::cout << foo; -// -// will print "1 != 2". -// -// Message is not intended to be inherited from. In particular, its -// destructor is not virtual. -// -// Note that stringstream behaves differently in gcc and in MSVC. You -// can stream a NULL char pointer to it in the former, but not in the -// latter (it causes an access violation if you do). The Message -// class hides this difference by treating a NULL char pointer as -// "(null)". -class GTEST_API_ Message { - private: - // The type of basic IO manipulators (endl, ends, and flush) for - // narrow streams. - typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); - - public: - // Constructs an empty Message. - Message(); - - // Copy constructor. - Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT - *ss_ << msg.GetString(); - } - - // Constructs a Message from a C-string. - explicit Message(const char* str) : ss_(new ::std::stringstream) { - *ss_ << str; - } - - // Streams a non-pointer value to this object. - template - inline Message& operator <<(const T& val) { - // Some libraries overload << for STL containers. These - // overloads are defined in the global namespace instead of ::std. - // - // C++'s symbol lookup rule (i.e. Koenig lookup) says that these - // overloads are visible in either the std namespace or the global - // namespace, but not other namespaces, including the testing - // namespace which Google Test's Message class is in. - // - // To allow STL containers (and other types that has a << operator - // defined in the global namespace) to be used in Google Test - // assertions, testing::Message must access the custom << operator - // from the global namespace. With this using declaration, - // overloads of << defined in the global namespace and those - // visible via Koenig lookup are both exposed in this function. - using ::operator <<; - *ss_ << val; - return *this; - } - - // Streams a pointer value to this object. - // - // This function is an overload of the previous one. When you - // stream a pointer to a Message, this definition will be used as it - // is more specialized. (The C++ Standard, section - // [temp.func.order].) If you stream a non-pointer, then the - // previous definition will be used. - // - // The reason for this overload is that streaming a NULL pointer to - // ostream is undefined behavior. Depending on the compiler, you - // may get "0", "(nil)", "(null)", or an access violation. To - // ensure consistent result across compilers, we always treat NULL - // as "(null)". - template - inline Message& operator <<(T* const& pointer) { // NOLINT - if (pointer == nullptr) { - *ss_ << "(null)"; - } else { - *ss_ << pointer; - } - return *this; - } - - // Since the basic IO manipulators are overloaded for both narrow - // and wide streams, we have to provide this specialized definition - // of operator <<, even though its body is the same as the - // templatized version above. Without this definition, streaming - // endl or other basic IO manipulators to Message will confuse the - // compiler. - Message& operator <<(BasicNarrowIoManip val) { - *ss_ << val; - return *this; - } - - // Instead of 1/0, we want to see true/false for bool values. - Message& operator <<(bool b) { - return *this << (b ? "true" : "false"); - } - - // These two overloads allow streaming a wide C string to a Message - // using the UTF-8 encoding. - Message& operator <<(const wchar_t* wide_c_str); - Message& operator <<(wchar_t* wide_c_str); - -#if GTEST_HAS_STD_WSTRING - // Converts the given wide string to a narrow string using the UTF-8 - // encoding, and streams the result to this Message object. - Message& operator <<(const ::std::wstring& wstr); -#endif // GTEST_HAS_STD_WSTRING - - // Gets the text streamed to this object so far as an std::string. - // Each '\0' character in the buffer is replaced with "\\0". - // - // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - std::string GetString() const; - - private: - // We'll hold the text streamed to this object here. - const std::unique_ptr< ::std::stringstream> ss_; - - // We declare (but don't implement) this to prevent the compiler - // from implementing the assignment operator. - void operator=(const Message&); -}; - -// Streams a Message to an ostream. -inline std::ostream& operator <<(std::ostream& os, const Message& sb) { - return os << sb.GetString(); -} - -namespace internal { - -// Converts a streamable value to an std::string. A NULL pointer is -// converted to "(null)". When the input value is a ::string, -// ::std::string, ::wstring, or ::std::wstring object, each NUL -// character in it is replaced with "\\0". -template -std::string StreamableToString(const T& streamable) { - return (Message() << streamable).GetString(); -} - -} // namespace internal -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Google Test filepath utilities -// -// This header file declares classes and functions used internally by -// Google Test. They are subject to change without notice. -// -// This file is #included in gtest/internal/gtest-internal.h. -// Do not include this header file separately! - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file declares the String class and functions used internally by -// Google Test. They are subject to change without notice. They should not used -// by code external to Google Test. -// -// This header file is #included by gtest-internal.h. -// It should not be #included by other files. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ - -#ifdef __BORLANDC__ -// string.h is not guaranteed to provide strcpy on C++ Builder. -# include -#endif - -#include -#include - - -namespace testing { -namespace internal { - -// String - an abstract class holding static string utilities. -class GTEST_API_ String { - public: - // Static utility methods - - // Clones a 0-terminated C string, allocating memory using new. The - // caller is responsible for deleting the return value using - // delete[]. Returns the cloned string, or NULL if the input is - // NULL. - // - // This is different from strdup() in string.h, which allocates - // memory using malloc(). - static const char* CloneCString(const char* c_str); - -#if GTEST_OS_WINDOWS_MOBILE - // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be - // able to pass strings to Win32 APIs on CE we need to convert them - // to 'Unicode', UTF-16. - - // Creates a UTF-16 wide string from the given ANSI string, allocating - // memory using new. The caller is responsible for deleting the return - // value using delete[]. Returns the wide string, or NULL if the - // input is NULL. - // - // The wide string is created using the ANSI codepage (CP_ACP) to - // match the behaviour of the ANSI versions of Win32 calls and the - // C runtime. - static LPCWSTR AnsiToUtf16(const char* c_str); - - // Creates an ANSI string from the given wide string, allocating - // memory using new. The caller is responsible for deleting the return - // value using delete[]. Returns the ANSI string, or NULL if the - // input is NULL. - // - // The returned string is created using the ANSI codepage (CP_ACP) to - // match the behaviour of the ANSI versions of Win32 calls and the - // C runtime. - static const char* Utf16ToAnsi(LPCWSTR utf16_str); -#endif - - // Compares two C strings. Returns true iff they have the same content. - // - // Unlike strcmp(), this function can handle NULL argument(s). A - // NULL C string is considered different to any non-NULL C string, - // including the empty string. - static bool CStringEquals(const char* lhs, const char* rhs); - - // Converts a wide C string to a String using the UTF-8 encoding. - // NULL will be converted to "(null)". If an error occurred during - // the conversion, "(failed to convert from wide string)" is - // returned. - static std::string ShowWideCString(const wchar_t* wide_c_str); - - // Compares two wide C strings. Returns true iff they have the same - // content. - // - // Unlike wcscmp(), this function can handle NULL argument(s). A - // NULL C string is considered different to any non-NULL C string, - // including the empty string. - static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); - - // Compares two C strings, ignoring case. Returns true iff they - // have the same content. - // - // Unlike strcasecmp(), this function can handle NULL argument(s). - // A NULL C string is considered different to any non-NULL C string, - // including the empty string. - static bool CaseInsensitiveCStringEquals(const char* lhs, - const char* rhs); - - // Compares two wide C strings, ignoring case. Returns true iff they - // have the same content. - // - // Unlike wcscasecmp(), this function can handle NULL argument(s). - // A NULL C string is considered different to any non-NULL wide C string, - // including the empty string. - // NB: The implementations on different platforms slightly differ. - // On windows, this method uses _wcsicmp which compares according to LC_CTYPE - // environment variable. On GNU platform this method uses wcscasecmp - // which compares according to LC_CTYPE category of the current locale. - // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the - // current locale. - static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, - const wchar_t* rhs); - - // Returns true iff the given string ends with the given suffix, ignoring - // case. Any string is considered to end with an empty suffix. - static bool EndsWithCaseInsensitive( - const std::string& str, const std::string& suffix); - - // Formats an int value as "%02d". - static std::string FormatIntWidth2(int value); // "%02d" for width == 2 - - // Formats an int value as "%X". - static std::string FormatHexInt(int value); - - // Formats an int value as "%X". - static std::string FormatHexUInt32(UInt32 value); - - // Formats a byte as "%02X". - static std::string FormatByte(unsigned char value); - - private: - String(); // Not meant to be instantiated. -}; // class String - -// Gets the content of the stringstream's buffer as an std::string. Each '\0' -// character in the buffer is replaced with "\\0". -GTEST_API_ std::string StringStreamToString(::std::stringstream* stream); - -} // namespace internal -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -namespace testing { -namespace internal { - -// FilePath - a class for file and directory pathname manipulation which -// handles platform-specific conventions (like the pathname separator). -// Used for helper functions for naming files in a directory for xml output. -// Except for Set methods, all methods are const or static, which provides an -// "immutable value object" -- useful for peace of mind. -// A FilePath with a value ending in a path separator ("like/this/") represents -// a directory, otherwise it is assumed to represent a file. In either case, -// it may or may not represent an actual file or directory in the file system. -// Names are NOT checked for syntax correctness -- no checking for illegal -// characters, malformed paths, etc. - -class GTEST_API_ FilePath { - public: - FilePath() : pathname_("") { } - FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } - - explicit FilePath(const std::string& pathname) : pathname_(pathname) { - Normalize(); - } - - FilePath& operator=(const FilePath& rhs) { - Set(rhs); - return *this; - } - - void Set(const FilePath& rhs) { - pathname_ = rhs.pathname_; - } - - const std::string& string() const { return pathname_; } - const char* c_str() const { return pathname_.c_str(); } - - // Returns the current working directory, or "" if unsuccessful. - static FilePath GetCurrentDir(); - - // Given directory = "dir", base_name = "test", number = 0, - // extension = "xml", returns "dir/test.xml". If number is greater - // than zero (e.g., 12), returns "dir/test_12.xml". - // On Windows platform, uses \ as the separator rather than /. - static FilePath MakeFileName(const FilePath& directory, - const FilePath& base_name, - int number, - const char* extension); - - // Given directory = "dir", relative_path = "test.xml", - // returns "dir/test.xml". - // On Windows, uses \ as the separator rather than /. - static FilePath ConcatPaths(const FilePath& directory, - const FilePath& relative_path); - - // Returns a pathname for a file that does not currently exist. The pathname - // will be directory/base_name.extension or - // directory/base_name_.extension if directory/base_name.extension - // already exists. The number will be incremented until a pathname is found - // that does not already exist. - // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. - // There could be a race condition if two or more processes are calling this - // function at the same time -- they could both pick the same filename. - static FilePath GenerateUniqueFileName(const FilePath& directory, - const FilePath& base_name, - const char* extension); - - // Returns true iff the path is "". - bool IsEmpty() const { return pathname_.empty(); } - - // If input name has a trailing separator character, removes it and returns - // the name, otherwise return the name string unmodified. - // On Windows platform, uses \ as the separator, other platforms use /. - FilePath RemoveTrailingPathSeparator() const; - - // Returns a copy of the FilePath with the directory part removed. - // Example: FilePath("path/to/file").RemoveDirectoryName() returns - // FilePath("file"). If there is no directory part ("just_a_file"), it returns - // the FilePath unmodified. If there is no file part ("just_a_dir/") it - // returns an empty FilePath (""). - // On Windows platform, '\' is the path separator, otherwise it is '/'. - FilePath RemoveDirectoryName() const; - - // RemoveFileName returns the directory path with the filename removed. - // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". - // If the FilePath is "a_file" or "/a_file", RemoveFileName returns - // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does - // not have a file, like "just/a/dir/", it returns the FilePath unmodified. - // On Windows platform, '\' is the path separator, otherwise it is '/'. - FilePath RemoveFileName() const; - - // Returns a copy of the FilePath with the case-insensitive extension removed. - // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns - // FilePath("dir/file"). If a case-insensitive extension is not - // found, returns a copy of the original FilePath. - FilePath RemoveExtension(const char* extension) const; - - // Creates directories so that path exists. Returns true if successful or if - // the directories already exist; returns false if unable to create - // directories for any reason. Will also return false if the FilePath does - // not represent a directory (that is, it doesn't end with a path separator). - bool CreateDirectoriesRecursively() const; - - // Create the directory so that path exists. Returns true if successful or - // if the directory already exists; returns false if unable to create the - // directory for any reason, including if the parent directory does not - // exist. Not named "CreateDirectory" because that's a macro on Windows. - bool CreateFolder() const; - - // Returns true if FilePath describes something in the file-system, - // either a file, directory, or whatever, and that something exists. - bool FileOrDirectoryExists() const; - - // Returns true if pathname describes a directory in the file-system - // that exists. - bool DirectoryExists() const; - - // Returns true if FilePath ends with a path separator, which indicates that - // it is intended to represent a directory. Returns false otherwise. - // This does NOT check that a directory (or file) actually exists. - bool IsDirectory() const; - - // Returns true if pathname describes a root directory. (Windows has one - // root directory per disk drive.) - bool IsRootDirectory() const; - - // Returns true if pathname describes an absolute path. - bool IsAbsolutePath() const; - - private: - // Replaces multiple consecutive separators with a single separator. - // For example, "bar///foo" becomes "bar/foo". Does not eliminate other - // redundancies that might be in a pathname involving "." or "..". - // - // A pathname with multiple consecutive separators may occur either through - // user error or as a result of some scripts or APIs that generate a pathname - // with a trailing separator. On other platforms the same API or script - // may NOT generate a pathname with a trailing "/". Then elsewhere that - // pathname may have another "/" and pathname components added to it, - // without checking for the separator already being there. - // The script language and operating system may allow paths like "foo//bar" - // but some of the functions in FilePath will not handle that correctly. In - // particular, RemoveTrailingPathSeparator() only removes one separator, and - // it is called in CreateDirectoriesRecursively() assuming that it will change - // a pathname from directory syntax (trailing separator) to filename syntax. - // - // On Windows this method also replaces the alternate path separator '/' with - // the primary path separator '\\', so that for example "bar\\/\\foo" becomes - // "bar\\foo". - - void Normalize(); - - // Returns a pointer to the last occurence of a valid path separator in - // the FilePath. On Windows, for example, both '/' and '\' are valid path - // separators. Returns NULL if no path separator was found. - const char* FindLastPathSeparator() const; - - std::string pathname_; -}; // class FilePath - -} // namespace internal -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ -// This file was GENERATED by command: -// pump.py gtest-type-util.h.pump -// DO NOT EDIT BY HAND!!! - -// Copyright 2008 Google Inc. -// All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Type utilities needed for implementing typed and type-parameterized -// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! -// -// Currently we support at most 50 types in a list, and at most 50 -// type-parameterized tests in one type-parameterized test suite. -// Please contact googletestframework@googlegroups.com if you need -// more. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ - - -// #ifdef __GNUC__ is too general here. It is possible to use gcc without using -// libstdc++ (which is where cxxabi.h comes from). -# if GTEST_HAS_CXXABI_H_ -# include -# elif defined(__HP_aCC) -# include -# endif // GTEST_HASH_CXXABI_H_ - -namespace testing { -namespace internal { - -// Canonicalizes a given name with respect to the Standard C++ Library. -// This handles removing the inline namespace within `std` that is -// used by various standard libraries (e.g., `std::__1`). Names outside -// of namespace std are returned unmodified. -inline std::string CanonicalizeForStdLibVersioning(std::string s) { - static const char prefix[] = "std::__"; - if (s.compare(0, strlen(prefix), prefix) == 0) { - std::string::size_type end = s.find("::", strlen(prefix)); - if (end != s.npos) { - // Erase everything between the initial `std` and the second `::`. - s.erase(strlen("std"), end - strlen("std")); - } - } - return s; -} - -// GetTypeName() returns a human-readable name of type T. -// NB: This function is also used in Google Mock, so don't move it inside of -// the typed-test-only section below. -template -std::string GetTypeName() { -# if GTEST_HAS_RTTI - - const char* const name = typeid(T).name(); -# if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC) - int status = 0; - // gcc's implementation of typeid(T).name() mangles the type name, - // so we have to demangle it. -# if GTEST_HAS_CXXABI_H_ - using abi::__cxa_demangle; -# endif // GTEST_HAS_CXXABI_H_ - char* const readable_name = __cxa_demangle(name, nullptr, nullptr, &status); - const std::string name_str(status == 0 ? readable_name : name); - free(readable_name); - return CanonicalizeForStdLibVersioning(name_str); -# else - return name; -# endif // GTEST_HAS_CXXABI_H_ || __HP_aCC - -# else - - return ""; - -# endif // GTEST_HAS_RTTI -} - -#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - -// AssertyTypeEq::type is defined iff T1 and T2 are the same -// type. This can be used as a compile-time assertion to ensure that -// two types are equal. - -template -struct AssertTypeEq; - -template -struct AssertTypeEq { - typedef bool type; -}; - -// A unique type used as the default value for the arguments of class -// template Types. This allows us to simulate variadic templates -// (e.g. Types, Type, and etc), which C++ doesn't -// support directly. -struct None {}; - -// The following family of struct and struct templates are used to -// represent type lists. In particular, TypesN -// represents a type list with N types (T1, T2, ..., and TN) in it. -// Except for Types0, every struct in the family has two member types: -// Head for the first type in the list, and Tail for the rest of the -// list. - -// The empty type list. -struct Types0 {}; - -// Type lists of length 1, 2, 3, and so on. - -template -struct Types1 { - typedef T1 Head; - typedef Types0 Tail; -}; -template -struct Types2 { - typedef T1 Head; - typedef Types1 Tail; -}; - -template -struct Types3 { - typedef T1 Head; - typedef Types2 Tail; -}; - -template -struct Types4 { - typedef T1 Head; - typedef Types3 Tail; -}; - -template -struct Types5 { - typedef T1 Head; - typedef Types4 Tail; -}; - -template -struct Types6 { - typedef T1 Head; - typedef Types5 Tail; -}; - -template -struct Types7 { - typedef T1 Head; - typedef Types6 Tail; -}; - -template -struct Types8 { - typedef T1 Head; - typedef Types7 Tail; -}; - -template -struct Types9 { - typedef T1 Head; - typedef Types8 Tail; -}; - -template -struct Types10 { - typedef T1 Head; - typedef Types9 Tail; -}; - -template -struct Types11 { - typedef T1 Head; - typedef Types10 Tail; -}; - -template -struct Types12 { - typedef T1 Head; - typedef Types11 Tail; -}; - -template -struct Types13 { - typedef T1 Head; - typedef Types12 Tail; -}; - -template -struct Types14 { - typedef T1 Head; - typedef Types13 Tail; -}; - -template -struct Types15 { - typedef T1 Head; - typedef Types14 Tail; -}; - -template -struct Types16 { - typedef T1 Head; - typedef Types15 Tail; -}; - -template -struct Types17 { - typedef T1 Head; - typedef Types16 Tail; -}; - -template -struct Types18 { - typedef T1 Head; - typedef Types17 Tail; -}; - -template -struct Types19 { - typedef T1 Head; - typedef Types18 Tail; -}; - -template -struct Types20 { - typedef T1 Head; - typedef Types19 Tail; -}; - -template -struct Types21 { - typedef T1 Head; - typedef Types20 Tail; -}; - -template -struct Types22 { - typedef T1 Head; - typedef Types21 Tail; -}; - -template -struct Types23 { - typedef T1 Head; - typedef Types22 Tail; -}; - -template -struct Types24 { - typedef T1 Head; - typedef Types23 Tail; -}; - -template -struct Types25 { - typedef T1 Head; - typedef Types24 Tail; -}; - -template -struct Types26 { - typedef T1 Head; - typedef Types25 Tail; -}; - -template -struct Types27 { - typedef T1 Head; - typedef Types26 Tail; -}; - -template -struct Types28 { - typedef T1 Head; - typedef Types27 Tail; -}; - -template -struct Types29 { - typedef T1 Head; - typedef Types28 Tail; -}; - -template -struct Types30 { - typedef T1 Head; - typedef Types29 Tail; -}; - -template -struct Types31 { - typedef T1 Head; - typedef Types30 Tail; -}; - -template -struct Types32 { - typedef T1 Head; - typedef Types31 Tail; -}; - -template -struct Types33 { - typedef T1 Head; - typedef Types32 Tail; -}; - -template -struct Types34 { - typedef T1 Head; - typedef Types33 Tail; -}; - -template -struct Types35 { - typedef T1 Head; - typedef Types34 Tail; -}; - -template -struct Types36 { - typedef T1 Head; - typedef Types35 Tail; -}; - -template -struct Types37 { - typedef T1 Head; - typedef Types36 Tail; -}; - -template -struct Types38 { - typedef T1 Head; - typedef Types37 Tail; -}; - -template -struct Types39 { - typedef T1 Head; - typedef Types38 Tail; -}; - -template -struct Types40 { - typedef T1 Head; - typedef Types39 Tail; -}; - -template -struct Types41 { - typedef T1 Head; - typedef Types40 Tail; -}; - -template -struct Types42 { - typedef T1 Head; - typedef Types41 Tail; -}; - -template -struct Types43 { - typedef T1 Head; - typedef Types42 Tail; -}; - -template -struct Types44 { - typedef T1 Head; - typedef Types43 Tail; -}; - -template -struct Types45 { - typedef T1 Head; - typedef Types44 Tail; -}; - -template -struct Types46 { - typedef T1 Head; - typedef Types45 Tail; -}; - -template -struct Types47 { - typedef T1 Head; - typedef Types46 Tail; -}; - -template -struct Types48 { - typedef T1 Head; - typedef Types47 Tail; -}; - -template -struct Types49 { - typedef T1 Head; - typedef Types48 Tail; -}; - -template -struct Types50 { - typedef T1 Head; - typedef Types49 Tail; -}; - - -} // namespace internal - -// We don't want to require the users to write TypesN<...> directly, -// as that would require them to count the length. Types<...> is much -// easier to write, but generates horrible messages when there is a -// compiler error, as gcc insists on printing out each template -// argument, even if it has the default value (this means Types -// will appear as Types in the compiler -// errors). -// -// Our solution is to combine the best part of the two approaches: a -// user would write Types, and Google Test will translate -// that to TypesN internally to make error messages -// readable. The translation is done by the 'type' member of the -// Types template. -template -struct Types { - typedef internal::Types50 type; -}; - -template <> -struct Types { - typedef internal::Types0 type; -}; -template -struct Types { - typedef internal::Types1 type; -}; -template -struct Types { - typedef internal::Types2 type; -}; -template -struct Types { - typedef internal::Types3 type; -}; -template -struct Types { - typedef internal::Types4 type; -}; -template -struct Types { - typedef internal::Types5 type; -}; -template -struct Types { - typedef internal::Types6 type; -}; -template -struct Types { - typedef internal::Types7 type; -}; -template -struct Types { - typedef internal::Types8 type; -}; -template -struct Types { - typedef internal::Types9 type; -}; -template -struct Types { - typedef internal::Types10 type; -}; -template -struct Types { - typedef internal::Types11 type; -}; -template -struct Types { - typedef internal::Types12 type; -}; -template -struct Types { - typedef internal::Types13 type; -}; -template -struct Types { - typedef internal::Types14 type; -}; -template -struct Types { - typedef internal::Types15 type; -}; -template -struct Types { - typedef internal::Types16 type; -}; -template -struct Types { - typedef internal::Types17 type; -}; -template -struct Types { - typedef internal::Types18 type; -}; -template -struct Types { - typedef internal::Types19 type; -}; -template -struct Types { - typedef internal::Types20 type; -}; -template -struct Types { - typedef internal::Types21 type; -}; -template -struct Types { - typedef internal::Types22 type; -}; -template -struct Types { - typedef internal::Types23 type; -}; -template -struct Types { - typedef internal::Types24 type; -}; -template -struct Types { - typedef internal::Types25 type; -}; -template -struct Types { - typedef internal::Types26 type; -}; -template -struct Types { - typedef internal::Types27 type; -}; -template -struct Types { - typedef internal::Types28 type; -}; -template -struct Types { - typedef internal::Types29 type; -}; -template -struct Types { - typedef internal::Types30 type; -}; -template -struct Types { - typedef internal::Types31 type; -}; -template -struct Types { - typedef internal::Types32 type; -}; -template -struct Types { - typedef internal::Types33 type; -}; -template -struct Types { - typedef internal::Types34 type; -}; -template -struct Types { - typedef internal::Types35 type; -}; -template -struct Types { - typedef internal::Types36 type; -}; -template -struct Types { - typedef internal::Types37 type; -}; -template -struct Types { - typedef internal::Types38 type; -}; -template -struct Types { - typedef internal::Types39 type; -}; -template -struct Types { - typedef internal::Types40 type; -}; -template -struct Types { - typedef internal::Types41 type; -}; -template -struct Types { - typedef internal::Types42 type; -}; -template -struct Types { - typedef internal::Types43 type; -}; -template -struct Types { - typedef internal::Types44 type; -}; -template -struct Types { - typedef internal::Types45 type; -}; -template -struct Types { - typedef internal::Types46 type; -}; -template -struct Types { - typedef internal::Types47 type; -}; -template -struct Types { - typedef internal::Types48 type; -}; -template -struct Types { - typedef internal::Types49 type; -}; - -namespace internal { - -# define GTEST_TEMPLATE_ template class - -// The template "selector" struct TemplateSel is used to -// represent Tmpl, which must be a class template with one type -// parameter, as a type. TemplateSel::Bind::type is defined -// as the type Tmpl. This allows us to actually instantiate the -// template "selected" by TemplateSel. -// -// This trick is necessary for simulating typedef for class templates, -// which C++ doesn't support directly. -template -struct TemplateSel { - template - struct Bind { - typedef Tmpl type; - }; -}; - -# define GTEST_BIND_(TmplSel, T) \ - TmplSel::template Bind::type - -// A unique struct template used as the default value for the -// arguments of class template Templates. This allows us to simulate -// variadic templates (e.g. Templates, Templates, -// and etc), which C++ doesn't support directly. -template -struct NoneT {}; - -// The following family of struct and struct templates are used to -// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except -// for Templates0, every struct in the family has two member types: -// Head for the selector of the first template in the list, and Tail -// for the rest of the list. - -// The empty template list. -struct Templates0 {}; - -// Template lists of length 1, 2, 3, and so on. - -template -struct Templates1 { - typedef TemplateSel Head; - typedef Templates0 Tail; -}; -template -struct Templates2 { - typedef TemplateSel Head; - typedef Templates1 Tail; -}; - -template -struct Templates3 { - typedef TemplateSel Head; - typedef Templates2 Tail; -}; - -template -struct Templates4 { - typedef TemplateSel Head; - typedef Templates3 Tail; -}; - -template -struct Templates5 { - typedef TemplateSel Head; - typedef Templates4 Tail; -}; - -template -struct Templates6 { - typedef TemplateSel Head; - typedef Templates5 Tail; -}; - -template -struct Templates7 { - typedef TemplateSel Head; - typedef Templates6 Tail; -}; - -template -struct Templates8 { - typedef TemplateSel Head; - typedef Templates7 Tail; -}; - -template -struct Templates9 { - typedef TemplateSel Head; - typedef Templates8 Tail; -}; - -template -struct Templates10 { - typedef TemplateSel Head; - typedef Templates9 Tail; -}; - -template -struct Templates11 { - typedef TemplateSel Head; - typedef Templates10 Tail; -}; - -template -struct Templates12 { - typedef TemplateSel Head; - typedef Templates11 Tail; -}; - -template -struct Templates13 { - typedef TemplateSel Head; - typedef Templates12 Tail; -}; - -template -struct Templates14 { - typedef TemplateSel Head; - typedef Templates13 Tail; -}; - -template -struct Templates15 { - typedef TemplateSel Head; - typedef Templates14 Tail; -}; - -template -struct Templates16 { - typedef TemplateSel Head; - typedef Templates15 Tail; -}; - -template -struct Templates17 { - typedef TemplateSel Head; - typedef Templates16 Tail; -}; - -template -struct Templates18 { - typedef TemplateSel Head; - typedef Templates17 Tail; -}; - -template -struct Templates19 { - typedef TemplateSel Head; - typedef Templates18 Tail; -}; - -template -struct Templates20 { - typedef TemplateSel Head; - typedef Templates19 Tail; -}; - -template -struct Templates21 { - typedef TemplateSel Head; - typedef Templates20 Tail; -}; - -template -struct Templates22 { - typedef TemplateSel Head; - typedef Templates21 Tail; -}; - -template -struct Templates23 { - typedef TemplateSel Head; - typedef Templates22 Tail; -}; - -template -struct Templates24 { - typedef TemplateSel Head; - typedef Templates23 Tail; -}; - -template -struct Templates25 { - typedef TemplateSel Head; - typedef Templates24 Tail; -}; - -template -struct Templates26 { - typedef TemplateSel Head; - typedef Templates25 Tail; -}; - -template -struct Templates27 { - typedef TemplateSel Head; - typedef Templates26 Tail; -}; - -template -struct Templates28 { - typedef TemplateSel Head; - typedef Templates27 Tail; -}; - -template -struct Templates29 { - typedef TemplateSel Head; - typedef Templates28 Tail; -}; - -template -struct Templates30 { - typedef TemplateSel Head; - typedef Templates29 Tail; -}; - -template -struct Templates31 { - typedef TemplateSel Head; - typedef Templates30 Tail; -}; - -template -struct Templates32 { - typedef TemplateSel Head; - typedef Templates31 Tail; -}; - -template -struct Templates33 { - typedef TemplateSel Head; - typedef Templates32 Tail; -}; - -template -struct Templates34 { - typedef TemplateSel Head; - typedef Templates33 Tail; -}; - -template -struct Templates35 { - typedef TemplateSel Head; - typedef Templates34 Tail; -}; - -template -struct Templates36 { - typedef TemplateSel Head; - typedef Templates35 Tail; -}; - -template -struct Templates37 { - typedef TemplateSel Head; - typedef Templates36 Tail; -}; - -template -struct Templates38 { - typedef TemplateSel Head; - typedef Templates37 Tail; -}; - -template -struct Templates39 { - typedef TemplateSel Head; - typedef Templates38 Tail; -}; - -template -struct Templates40 { - typedef TemplateSel Head; - typedef Templates39 Tail; -}; - -template -struct Templates41 { - typedef TemplateSel Head; - typedef Templates40 Tail; -}; - -template -struct Templates42 { - typedef TemplateSel Head; - typedef Templates41 Tail; -}; - -template -struct Templates43 { - typedef TemplateSel Head; - typedef Templates42 Tail; -}; - -template -struct Templates44 { - typedef TemplateSel Head; - typedef Templates43 Tail; -}; - -template -struct Templates45 { - typedef TemplateSel Head; - typedef Templates44 Tail; -}; - -template -struct Templates46 { - typedef TemplateSel Head; - typedef Templates45 Tail; -}; - -template -struct Templates47 { - typedef TemplateSel Head; - typedef Templates46 Tail; -}; - -template -struct Templates48 { - typedef TemplateSel Head; - typedef Templates47 Tail; -}; - -template -struct Templates49 { - typedef TemplateSel Head; - typedef Templates48 Tail; -}; - -template -struct Templates50 { - typedef TemplateSel Head; - typedef Templates49 Tail; -}; - - -// We don't want to require the users to write TemplatesN<...> directly, -// as that would require them to count the length. Templates<...> is much -// easier to write, but generates horrible messages when there is a -// compiler error, as gcc insists on printing out each template -// argument, even if it has the default value (this means Templates -// will appear as Templates in the compiler -// errors). -// -// Our solution is to combine the best part of the two approaches: a -// user would write Templates, and Google Test will translate -// that to TemplatesN internally to make error messages -// readable. The translation is done by the 'type' member of the -// Templates template. -template -struct Templates { - typedef Templates50 type; -}; - -template <> -struct Templates { - typedef Templates0 type; -}; -template -struct Templates { - typedef Templates1 type; -}; -template -struct Templates { - typedef Templates2 type; -}; -template -struct Templates { - typedef Templates3 type; -}; -template -struct Templates { - typedef Templates4 type; -}; -template -struct Templates { - typedef Templates5 type; -}; -template -struct Templates { - typedef Templates6 type; -}; -template -struct Templates { - typedef Templates7 type; -}; -template -struct Templates { - typedef Templates8 type; -}; -template -struct Templates { - typedef Templates9 type; -}; -template -struct Templates { - typedef Templates10 type; -}; -template -struct Templates { - typedef Templates11 type; -}; -template -struct Templates { - typedef Templates12 type; -}; -template -struct Templates { - typedef Templates13 type; -}; -template -struct Templates { - typedef Templates14 type; -}; -template -struct Templates { - typedef Templates15 type; -}; -template -struct Templates { - typedef Templates16 type; -}; -template -struct Templates { - typedef Templates17 type; -}; -template -struct Templates { - typedef Templates18 type; -}; -template -struct Templates { - typedef Templates19 type; -}; -template -struct Templates { - typedef Templates20 type; -}; -template -struct Templates { - typedef Templates21 type; -}; -template -struct Templates { - typedef Templates22 type; -}; -template -struct Templates { - typedef Templates23 type; -}; -template -struct Templates { - typedef Templates24 type; -}; -template -struct Templates { - typedef Templates25 type; -}; -template -struct Templates { - typedef Templates26 type; -}; -template -struct Templates { - typedef Templates27 type; -}; -template -struct Templates { - typedef Templates28 type; -}; -template -struct Templates { - typedef Templates29 type; -}; -template -struct Templates { - typedef Templates30 type; -}; -template -struct Templates { - typedef Templates31 type; -}; -template -struct Templates { - typedef Templates32 type; -}; -template -struct Templates { - typedef Templates33 type; -}; -template -struct Templates { - typedef Templates34 type; -}; -template -struct Templates { - typedef Templates35 type; -}; -template -struct Templates { - typedef Templates36 type; -}; -template -struct Templates { - typedef Templates37 type; -}; -template -struct Templates { - typedef Templates38 type; -}; -template -struct Templates { - typedef Templates39 type; -}; -template -struct Templates { - typedef Templates40 type; -}; -template -struct Templates { - typedef Templates41 type; -}; -template -struct Templates { - typedef Templates42 type; -}; -template -struct Templates { - typedef Templates43 type; -}; -template -struct Templates { - typedef Templates44 type; -}; -template -struct Templates { - typedef Templates45 type; -}; -template -struct Templates { - typedef Templates46 type; -}; -template -struct Templates { - typedef Templates47 type; -}; -template -struct Templates { - typedef Templates48 type; -}; -template -struct Templates { - typedef Templates49 type; -}; - -// The TypeList template makes it possible to use either a single type -// or a Types<...> list in TYPED_TEST_SUITE() and -// INSTANTIATE_TYPED_TEST_SUITE_P(). - -template -struct TypeList { - typedef Types1 type; -}; - -template -struct TypeList > { - typedef typename Types::type type; -}; - -#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - -} // namespace internal -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ - -// Due to C++ preprocessor weirdness, we need double indirection to -// concatenate two tokens when one of them is __LINE__. Writing -// -// foo ## __LINE__ -// -// will result in the token foo__LINE__, instead of foo followed by -// the current line number. For more details, see -// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 -#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) -#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar - -// Stringifies its argument. -#define GTEST_STRINGIFY_(name) #name - -namespace proto2 { class Message; } - -namespace testing { - -// Forward declarations. - -class AssertionResult; // Result of an assertion. -class Message; // Represents a failure message. -class Test; // Represents a test. -class TestInfo; // Information about a test. -class TestPartResult; // Result of a test part. -class UnitTest; // A collection of test suites. - -template -::std::string PrintToString(const T& value); - -namespace internal { - -struct TraceInfo; // Information about a trace point. -class TestInfoImpl; // Opaque implementation of TestInfo -class UnitTestImpl; // Opaque implementation of UnitTest - -// The text used in failure messages to indicate the start of the -// stack trace. -GTEST_API_ extern const char kStackTraceMarker[]; - -// An IgnoredValue object can be implicitly constructed from ANY value. -class IgnoredValue { - struct Sink {}; - public: - // This constructor template allows any value to be implicitly - // converted to IgnoredValue. The object has no data member and - // doesn't try to remember anything about the argument. We - // deliberately omit the 'explicit' keyword in order to allow the - // conversion to be implicit. - // Disable the conversion if T already has a magical conversion operator. - // Otherwise we get ambiguity. - template ::value, - int>::type = 0> - IgnoredValue(const T& /* ignored */) {} // NOLINT(runtime/explicit) -}; - -// Appends the user-supplied message to the Google-Test-generated message. -GTEST_API_ std::string AppendUserMessage( - const std::string& gtest_msg, const Message& user_msg); - -#if GTEST_HAS_EXCEPTIONS - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4275 \ -/* an exported class was derived from a class that was not exported */) - -// This exception is thrown by (and only by) a failed Google Test -// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions -// are enabled). We derive it from std::runtime_error, which is for -// errors presumably detectable only at run time. Since -// std::runtime_error inherits from std::exception, many testing -// frameworks know how to extract and print the message inside it. -class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error { - public: - explicit GoogleTestFailureException(const TestPartResult& failure); -}; - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4275 - -#endif // GTEST_HAS_EXCEPTIONS - -namespace edit_distance { -// Returns the optimal edits to go from 'left' to 'right'. -// All edits cost the same, with replace having lower priority than -// add/remove. -// Simple implementation of the Wagner-Fischer algorithm. -// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm -enum EditType { kMatch, kAdd, kRemove, kReplace }; -GTEST_API_ std::vector CalculateOptimalEdits( - const std::vector& left, const std::vector& right); - -// Same as above, but the input is represented as strings. -GTEST_API_ std::vector CalculateOptimalEdits( - const std::vector& left, - const std::vector& right); - -// Create a diff of the input strings in Unified diff format. -GTEST_API_ std::string CreateUnifiedDiff(const std::vector& left, - const std::vector& right, - size_t context = 2); - -} // namespace edit_distance - -// Calculate the diff between 'left' and 'right' and return it in unified diff -// format. -// If not null, stores in 'total_line_count' the total number of lines found -// in left + right. -GTEST_API_ std::string DiffStrings(const std::string& left, - const std::string& right, - size_t* total_line_count); - -// Constructs and returns the message for an equality assertion -// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. -// -// The first four parameters are the expressions used in the assertion -// and their values, as strings. For example, for ASSERT_EQ(foo, bar) -// where foo is 5 and bar is 6, we have: -// -// expected_expression: "foo" -// actual_expression: "bar" -// expected_value: "5" -// actual_value: "6" -// -// The ignoring_case parameter is true iff the assertion is a -// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will -// be inserted into the message. -GTEST_API_ AssertionResult EqFailure(const char* expected_expression, - const char* actual_expression, - const std::string& expected_value, - const std::string& actual_value, - bool ignoring_case); - -// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. -GTEST_API_ std::string GetBoolAssertionFailureMessage( - const AssertionResult& assertion_result, - const char* expression_text, - const char* actual_predicate_value, - const char* expected_predicate_value); - -// This template class represents an IEEE floating-point number -// (either single-precision or double-precision, depending on the -// template parameters). -// -// The purpose of this class is to do more sophisticated number -// comparison. (Due to round-off error, etc, it's very unlikely that -// two floating-points will be equal exactly. Hence a naive -// comparison by the == operation often doesn't work.) -// -// Format of IEEE floating-point: -// -// The most-significant bit being the leftmost, an IEEE -// floating-point looks like -// -// sign_bit exponent_bits fraction_bits -// -// Here, sign_bit is a single bit that designates the sign of the -// number. -// -// For float, there are 8 exponent bits and 23 fraction bits. -// -// For double, there are 11 exponent bits and 52 fraction bits. -// -// More details can be found at -// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. -// -// Template parameter: -// -// RawType: the raw floating-point type (either float or double) -template -class FloatingPoint { - public: - // Defines the unsigned integer type that has the same size as the - // floating point number. - typedef typename TypeWithSize::UInt Bits; - - // Constants. - - // # of bits in a number. - static const size_t kBitCount = 8*sizeof(RawType); - - // # of fraction bits in a number. - static const size_t kFractionBitCount = - std::numeric_limits::digits - 1; - - // # of exponent bits in a number. - static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; - - // The mask for the sign bit. - static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); - - // The mask for the fraction bits. - static const Bits kFractionBitMask = - ~static_cast(0) >> (kExponentBitCount + 1); - - // The mask for the exponent bits. - static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); - - // How many ULP's (Units in the Last Place) we want to tolerate when - // comparing two numbers. The larger the value, the more error we - // allow. A 0 value means that two numbers must be exactly the same - // to be considered equal. - // - // The maximum error of a single floating-point operation is 0.5 - // units in the last place. On Intel CPU's, all floating-point - // calculations are done with 80-bit precision, while double has 64 - // bits. Therefore, 4 should be enough for ordinary use. - // - // See the following article for more details on ULP: - // http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - static const size_t kMaxUlps = 4; - - // Constructs a FloatingPoint from a raw floating-point number. - // - // On an Intel CPU, passing a non-normalized NAN (Not a Number) - // around may change its bits, although the new value is guaranteed - // to be also a NAN. Therefore, don't expect this constructor to - // preserve the bits in x when x is a NAN. - explicit FloatingPoint(const RawType& x) { u_.value_ = x; } - - // Static methods - - // Reinterprets a bit pattern as a floating-point number. - // - // This function is needed to test the AlmostEquals() method. - static RawType ReinterpretBits(const Bits bits) { - FloatingPoint fp(0); - fp.u_.bits_ = bits; - return fp.u_.value_; - } - - // Returns the floating-point number that represent positive infinity. - static RawType Infinity() { - return ReinterpretBits(kExponentBitMask); - } - - // Returns the maximum representable finite floating-point number. - static RawType Max(); - - // Non-static methods - - // Returns the bits that represents this number. - const Bits &bits() const { return u_.bits_; } - - // Returns the exponent bits of this number. - Bits exponent_bits() const { return kExponentBitMask & u_.bits_; } - - // Returns the fraction bits of this number. - Bits fraction_bits() const { return kFractionBitMask & u_.bits_; } - - // Returns the sign bit of this number. - Bits sign_bit() const { return kSignBitMask & u_.bits_; } - - // Returns true iff this is NAN (not a number). - bool is_nan() const { - // It's a NAN if the exponent bits are all ones and the fraction - // bits are not entirely zeros. - return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); - } - - // Returns true iff this number is at most kMaxUlps ULP's away from - // rhs. In particular, this function: - // - // - returns false if either number is (or both are) NAN. - // - treats really large numbers as almost equal to infinity. - // - thinks +0.0 and -0.0 are 0 DLP's apart. - bool AlmostEquals(const FloatingPoint& rhs) const { - // The IEEE standard says that any comparison operation involving - // a NAN must return false. - if (is_nan() || rhs.is_nan()) return false; - - return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) - <= kMaxUlps; - } - - private: - // The data type used to store the actual floating-point number. - union FloatingPointUnion { - RawType value_; // The raw floating-point number. - Bits bits_; // The bits that represent the number. - }; - - // Converts an integer from the sign-and-magnitude representation to - // the biased representation. More precisely, let N be 2 to the - // power of (kBitCount - 1), an integer x is represented by the - // unsigned number x + N. - // - // For instance, - // - // -N + 1 (the most negative number representable using - // sign-and-magnitude) is represented by 1; - // 0 is represented by N; and - // N - 1 (the biggest number representable using - // sign-and-magnitude) is represented by 2N - 1. - // - // Read http://en.wikipedia.org/wiki/Signed_number_representations - // for more details on signed number representations. - static Bits SignAndMagnitudeToBiased(const Bits &sam) { - if (kSignBitMask & sam) { - // sam represents a negative number. - return ~sam + 1; - } else { - // sam represents a positive number. - return kSignBitMask | sam; - } - } - - // Given two numbers in the sign-and-magnitude representation, - // returns the distance between them as an unsigned number. - static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, - const Bits &sam2) { - const Bits biased1 = SignAndMagnitudeToBiased(sam1); - const Bits biased2 = SignAndMagnitudeToBiased(sam2); - return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); - } - - FloatingPointUnion u_; -}; - -// We cannot use std::numeric_limits::max() as it clashes with the max() -// macro defined by . -template <> -inline float FloatingPoint::Max() { return FLT_MAX; } -template <> -inline double FloatingPoint::Max() { return DBL_MAX; } - -// Typedefs the instances of the FloatingPoint template class that we -// care to use. -typedef FloatingPoint Float; -typedef FloatingPoint Double; - -// In order to catch the mistake of putting tests that use different -// test fixture classes in the same test suite, we need to assign -// unique IDs to fixture classes and compare them. The TypeId type is -// used to hold such IDs. The user should treat TypeId as an opaque -// type: the only operation allowed on TypeId values is to compare -// them for equality using the == operator. -typedef const void* TypeId; - -template -class TypeIdHelper { - public: - // dummy_ must not have a const type. Otherwise an overly eager - // compiler (e.g. MSVC 7.1 & 8.0) may try to merge - // TypeIdHelper::dummy_ for different Ts as an "optimization". - static bool dummy_; -}; - -template -bool TypeIdHelper::dummy_ = false; - -// GetTypeId() returns the ID of type T. Different values will be -// returned for different types. Calling the function twice with the -// same type argument is guaranteed to return the same ID. -template -TypeId GetTypeId() { - // The compiler is required to allocate a different - // TypeIdHelper::dummy_ variable for each T used to instantiate - // the template. Therefore, the address of dummy_ is guaranteed to - // be unique. - return &(TypeIdHelper::dummy_); -} - -// Returns the type ID of ::testing::Test. Always call this instead -// of GetTypeId< ::testing::Test>() to get the type ID of -// ::testing::Test, as the latter may give the wrong result due to a -// suspected linker bug when compiling Google Test as a Mac OS X -// framework. -GTEST_API_ TypeId GetTestTypeId(); - -// Defines the abstract factory interface that creates instances -// of a Test object. -class TestFactoryBase { - public: - virtual ~TestFactoryBase() {} - - // Creates a test instance to run. The instance is both created and destroyed - // within TestInfoImpl::Run() - virtual Test* CreateTest() = 0; - - protected: - TestFactoryBase() {} - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); -}; - -// This class provides implementation of TeastFactoryBase interface. -// It is used in TEST and TEST_F macros. -template -class TestFactoryImpl : public TestFactoryBase { - public: - Test* CreateTest() override { return new TestClass; } -}; - -#if GTEST_OS_WINDOWS - -// Predicate-formatters for implementing the HRESULT checking macros -// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} -// We pass a long instead of HRESULT to avoid causing an -// include dependency for the HRESULT type. -GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, - long hr); // NOLINT -GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, - long hr); // NOLINT - -#endif // GTEST_OS_WINDOWS - -// Types of SetUpTestSuite() and TearDownTestSuite() functions. -using SetUpTestSuiteFunc = void (*)(); -using TearDownTestSuiteFunc = void (*)(); - -struct CodeLocation { - CodeLocation(const std::string& a_file, int a_line) - : file(a_file), line(a_line) {} - - std::string file; - int line; -}; - -// Helper to identify which setup function for TestCase / TestSuite to call. -// Only one function is allowed, either TestCase or TestSute but not both. - -// Utility functions to help SuiteApiResolver -using SetUpTearDownSuiteFuncType = void (*)(); - -inline SetUpTearDownSuiteFuncType GetNotDefaultOrNull( - SetUpTearDownSuiteFuncType a, SetUpTearDownSuiteFuncType def) { - return a == def ? nullptr : a; -} - -template -// Note that SuiteApiResolver inherits from T because -// SetUpTestSuite()/TearDownTestSuite() could be protected. Ths way -// SuiteApiResolver can access them. -struct SuiteApiResolver : T { - // testing::Test is only forward declared at this point. So we make it a - // dependend class for the compiler to be OK with it. - using Test = - typename std::conditional::type; - - static SetUpTearDownSuiteFuncType GetSetUpCaseOrSuite(const char* filename, - int line_num) { - SetUpTearDownSuiteFuncType test_case_fp = - GetNotDefaultOrNull(&T::SetUpTestCase, &Test::SetUpTestCase); - SetUpTearDownSuiteFuncType test_suite_fp = - GetNotDefaultOrNull(&T::SetUpTestSuite, &Test::SetUpTestSuite); - - GTEST_CHECK_(!test_case_fp || !test_suite_fp) - << "Test can not provide both SetUpTestSuite and SetUpTestCase, please " - "make sure there is only one present at " - << filename << ":" << line_num; - - return test_case_fp != nullptr ? test_case_fp : test_suite_fp; - } - - static SetUpTearDownSuiteFuncType GetTearDownCaseOrSuite(const char* filename, - int line_num) { - SetUpTearDownSuiteFuncType test_case_fp = - GetNotDefaultOrNull(&T::TearDownTestCase, &Test::TearDownTestCase); - SetUpTearDownSuiteFuncType test_suite_fp = - GetNotDefaultOrNull(&T::TearDownTestSuite, &Test::TearDownTestSuite); - - GTEST_CHECK_(!test_case_fp || !test_suite_fp) - << "Test can not provide both TearDownTestSuite and TearDownTestCase," - " please make sure there is only one present at" - << filename << ":" << line_num; - - return test_case_fp != nullptr ? test_case_fp : test_suite_fp; - } -}; - -// Creates a new TestInfo object and registers it with Google Test; -// returns the created object. -// -// Arguments: -// -// test_suite_name: name of the test suite -// name: name of the test -// type_param the name of the test's type parameter, or NULL if -// this is not a typed or a type-parameterized test. -// value_param text representation of the test's value parameter, -// or NULL if this is not a type-parameterized test. -// code_location: code location where the test is defined -// fixture_class_id: ID of the test fixture class -// set_up_tc: pointer to the function that sets up the test suite -// tear_down_tc: pointer to the function that tears down the test suite -// factory: pointer to the factory that creates a test object. -// The newly created TestInfo instance will assume -// ownership of the factory object. -GTEST_API_ TestInfo* MakeAndRegisterTestInfo( - const char* test_suite_name, const char* name, const char* type_param, - const char* value_param, CodeLocation code_location, - TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc, - TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory); - -// If *pstr starts with the given prefix, modifies *pstr to be right -// past the prefix and returns true; otherwise leaves *pstr unchanged -// and returns false. None of pstr, *pstr, and prefix can be NULL. -GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); - -#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -// State of the definition of a type-parameterized test suite. -class GTEST_API_ TypedTestSuitePState { - public: - TypedTestSuitePState() : registered_(false) {} - - // Adds the given test name to defined_test_names_ and return true - // if the test suite hasn't been registered; otherwise aborts the - // program. - bool AddTestName(const char* file, int line, const char* case_name, - const char* test_name) { - if (registered_) { - fprintf(stderr, - "%s Test %s must be defined before " - "REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n", - FormatFileLocation(file, line).c_str(), test_name, case_name); - fflush(stderr); - posix::Abort(); - } - registered_tests_.insert( - ::std::make_pair(test_name, CodeLocation(file, line))); - return true; - } - - bool TestExists(const std::string& test_name) const { - return registered_tests_.count(test_name) > 0; - } - - const CodeLocation& GetCodeLocation(const std::string& test_name) const { - RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name); - GTEST_CHECK_(it != registered_tests_.end()); - return it->second; - } - - // Verifies that registered_tests match the test names in - // defined_test_names_; returns registered_tests if successful, or - // aborts the program otherwise. - const char* VerifyRegisteredTestNames( - const char* file, int line, const char* registered_tests); - - private: - typedef ::std::map RegisteredTestsMap; - - bool registered_; - RegisteredTestsMap registered_tests_; -}; - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -using TypedTestCasePState = TypedTestSuitePState; -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -// Skips to the first non-space char after the first comma in 'str'; -// returns NULL if no comma is found in 'str'. -inline const char* SkipComma(const char* str) { - const char* comma = strchr(str, ','); - if (comma == nullptr) { - return nullptr; - } - while (IsSpace(*(++comma))) {} - return comma; -} - -// Returns the prefix of 'str' before the first comma in it; returns -// the entire string if it contains no comma. -inline std::string GetPrefixUntilComma(const char* str) { - const char* comma = strchr(str, ','); - return comma == nullptr ? str : std::string(str, comma); -} - -// Splits a given string on a given delimiter, populating a given -// vector with the fields. -void SplitString(const ::std::string& str, char delimiter, - ::std::vector< ::std::string>* dest); - -// The default argument to the template below for the case when the user does -// not provide a name generator. -struct DefaultNameGenerator { - template - static std::string GetName(int i) { - return StreamableToString(i); - } -}; - -template -struct NameGeneratorSelector { - typedef Provided type; -}; - -template -void GenerateNamesRecursively(Types0, std::vector*, int) {} - -template -void GenerateNamesRecursively(Types, std::vector* result, int i) { - result->push_back(NameGenerator::template GetName(i)); - GenerateNamesRecursively(typename Types::Tail(), result, - i + 1); -} - -template -std::vector GenerateNames() { - std::vector result; - GenerateNamesRecursively(Types(), &result, 0); - return result; -} - -// TypeParameterizedTest::Register() -// registers a list of type-parameterized tests with Google Test. The -// return value is insignificant - we just need to return something -// such that we can call this function in a namespace scope. -// -// Implementation note: The GTEST_TEMPLATE_ macro declares a template -// template parameter. It's defined in gtest-type-util.h. -template -class TypeParameterizedTest { - public: - // 'index' is the index of the test in the type list 'Types' - // specified in INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, TestSuite, - // Types). Valid values for 'index' are [0, N - 1] where N is the - // length of Types. - static bool Register(const char* prefix, const CodeLocation& code_location, - const char* case_name, const char* test_names, int index, - const std::vector& type_names = - GenerateNames()) { - typedef typename Types::Head Type; - typedef Fixture FixtureClass; - typedef typename GTEST_BIND_(TestSel, Type) TestClass; - - // First, registers the first type-parameterized test in the type - // list. - MakeAndRegisterTestInfo( - (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + - "/" + type_names[static_cast(index)]) - .c_str(), - StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(), - GetTypeName().c_str(), - nullptr, // No value parameter. - code_location, GetTypeId(), - SuiteApiResolver::GetSetUpCaseOrSuite( - code_location.file.c_str(), code_location.line), - SuiteApiResolver::GetTearDownCaseOrSuite( - code_location.file.c_str(), code_location.line), - new TestFactoryImpl); - - // Next, recurses (at compile time) with the tail of the type list. - return TypeParameterizedTest::Register(prefix, - code_location, - case_name, - test_names, - index + 1, - type_names); - } -}; - -// The base case for the compile time recursion. -template -class TypeParameterizedTest { - public: - static bool Register(const char* /*prefix*/, const CodeLocation&, - const char* /*case_name*/, const char* /*test_names*/, - int /*index*/, - const std::vector& = - std::vector() /*type_names*/) { - return true; - } -}; - -// TypeParameterizedTestSuite::Register() -// registers *all combinations* of 'Tests' and 'Types' with Google -// Test. The return value is insignificant - we just need to return -// something such that we can call this function in a namespace scope. -template -class TypeParameterizedTestSuite { - public: - static bool Register(const char* prefix, CodeLocation code_location, - const TypedTestSuitePState* state, const char* case_name, - const char* test_names, - const std::vector& type_names = - GenerateNames()) { - std::string test_name = StripTrailingSpaces( - GetPrefixUntilComma(test_names)); - if (!state->TestExists(test_name)) { - fprintf(stderr, "Failed to get code location for test %s.%s at %s.", - case_name, test_name.c_str(), - FormatFileLocation(code_location.file.c_str(), - code_location.line).c_str()); - fflush(stderr); - posix::Abort(); - } - const CodeLocation& test_location = state->GetCodeLocation(test_name); - - typedef typename Tests::Head Head; - - // First, register the first test in 'Test' for each type in 'Types'. - TypeParameterizedTest::Register( - prefix, test_location, case_name, test_names, 0, type_names); - - // Next, recurses (at compile time) with the tail of the test list. - return TypeParameterizedTestSuite::Register(prefix, code_location, - state, case_name, - SkipComma(test_names), - type_names); - } -}; - -// The base case for the compile time recursion. -template -class TypeParameterizedTestSuite { - public: - static bool Register(const char* /*prefix*/, const CodeLocation&, - const TypedTestSuitePState* /*state*/, - const char* /*case_name*/, const char* /*test_names*/, - const std::vector& = - std::vector() /*type_names*/) { - return true; - } -}; - -#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P - -// Returns the current OS stack trace as an std::string. -// -// The maximum number of stack frames to be included is specified by -// the gtest_stack_trace_depth flag. The skip_count parameter -// specifies the number of top frames to be skipped, which doesn't -// count against the number of frames to be included. -// -// For example, if Foo() calls Bar(), which in turn calls -// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in -// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. -GTEST_API_ std::string GetCurrentOsStackTraceExceptTop( - UnitTest* unit_test, int skip_count); - -// Helpers for suppressing warnings on unreachable code or constant -// condition. - -// Always returns true. -GTEST_API_ bool AlwaysTrue(); - -// Always returns false. -inline bool AlwaysFalse() { return !AlwaysTrue(); } - -// Helper for suppressing false warning from Clang on a const char* -// variable declared in a conditional expression always being NULL in -// the else branch. -struct GTEST_API_ ConstCharPtr { - ConstCharPtr(const char* str) : value(str) {} - operator bool() const { return true; } - const char* value; -}; - -// A simple Linear Congruential Generator for generating random -// numbers with a uniform distribution. Unlike rand() and srand(), it -// doesn't use global state (and therefore can't interfere with user -// code). Unlike rand_r(), it's portable. An LCG isn't very random, -// but it's good enough for our purposes. -class GTEST_API_ Random { - public: - static const UInt32 kMaxRange = 1u << 31; - - explicit Random(UInt32 seed) : state_(seed) {} - - void Reseed(UInt32 seed) { state_ = seed; } - - // Generates a random number from [0, range). Crashes if 'range' is - // 0 or greater than kMaxRange. - UInt32 Generate(UInt32 range); - - private: - UInt32 state_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); -}; - -// Defining a variable of type CompileAssertTypesEqual will cause a -// compiler error iff T1 and T2 are different types. -template -struct CompileAssertTypesEqual; - -template -struct CompileAssertTypesEqual { -}; - -// Removes the reference from a type if it is a reference type, -// otherwise leaves it unchanged. This is the same as -// tr1::remove_reference, which is not widely available yet. -template -struct RemoveReference { typedef T type; }; // NOLINT -template -struct RemoveReference { typedef T type; }; // NOLINT - -// A handy wrapper around RemoveReference that works when the argument -// T depends on template parameters. -#define GTEST_REMOVE_REFERENCE_(T) \ - typename ::testing::internal::RemoveReference::type - -// Removes const from a type if it is a const type, otherwise leaves -// it unchanged. This is the same as tr1::remove_const, which is not -// widely available yet. -template -struct RemoveConst { typedef T type; }; // NOLINT -template -struct RemoveConst { typedef T type; }; // NOLINT - -// MSVC 8.0, Sun C++, and IBM XL C++ have a bug which causes the above -// definition to fail to remove the const in 'const int[3]' and 'const -// char[3][4]'. The following specialization works around the bug. -template -struct RemoveConst { - typedef typename RemoveConst::type type[N]; -}; - -// A handy wrapper around RemoveConst that works when the argument -// T depends on template parameters. -#define GTEST_REMOVE_CONST_(T) \ - typename ::testing::internal::RemoveConst::type - -// Turns const U&, U&, const U, and U all into U. -#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ - GTEST_REMOVE_CONST_(GTEST_REMOVE_REFERENCE_(T)) - -// IsAProtocolMessage::value is a compile-time bool constant that's -// true iff T is type proto2::Message or a subclass of it. -template -struct IsAProtocolMessage - : public bool_constant< - std::is_convertible::value> { -}; - -// When the compiler sees expression IsContainerTest(0), if C is an -// STL-style container class, the first overload of IsContainerTest -// will be viable (since both C::iterator* and C::const_iterator* are -// valid types and NULL can be implicitly converted to them). It will -// be picked over the second overload as 'int' is a perfect match for -// the type of argument 0. If C::iterator or C::const_iterator is not -// a valid type, the first overload is not viable, and the second -// overload will be picked. Therefore, we can determine whether C is -// a container class by checking the type of IsContainerTest(0). -// The value of the expression is insignificant. -// -// In C++11 mode we check the existence of a const_iterator and that an -// iterator is properly implemented for the container. -// -// For pre-C++11 that we look for both C::iterator and C::const_iterator. -// The reason is that C++ injects the name of a class as a member of the -// class itself (e.g. you can refer to class iterator as either -// 'iterator' or 'iterator::iterator'). If we look for C::iterator -// only, for example, we would mistakenly think that a class named -// iterator is an STL container. -// -// Also note that the simpler approach of overloading -// IsContainerTest(typename C::const_iterator*) and -// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. -typedef int IsContainer; -template ().begin()), - class = decltype(::std::declval().end()), - class = decltype(++::std::declval()), - class = decltype(*::std::declval()), - class = typename C::const_iterator> -IsContainer IsContainerTest(int /* dummy */) { - return 0; -} - -typedef char IsNotContainer; -template -IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; } - -// Trait to detect whether a type T is a hash table. -// The heuristic used is that the type contains an inner type `hasher` and does -// not contain an inner type `reverse_iterator`. -// If the container is iterable in reverse, then order might actually matter. -template -struct IsHashTable { - private: - template - static char test(typename U::hasher*, typename U::reverse_iterator*); - template - static int test(typename U::hasher*, ...); - template - static char test(...); - - public: - static const bool value = sizeof(test(nullptr, nullptr)) == sizeof(int); -}; - -template -const bool IsHashTable::value; - -template (0)) == sizeof(IsContainer)> -struct IsRecursiveContainerImpl; - -template -struct IsRecursiveContainerImpl : public false_type {}; - -// Since the IsRecursiveContainerImpl depends on the IsContainerTest we need to -// obey the same inconsistencies as the IsContainerTest, namely check if -// something is a container is relying on only const_iterator in C++11 and -// is relying on both const_iterator and iterator otherwise -template -struct IsRecursiveContainerImpl { - using value_type = decltype(*std::declval()); - using type = - is_same::type>::type, - C>; -}; - -// IsRecursiveContainer is a unary compile-time predicate that -// evaluates whether C is a recursive container type. A recursive container -// type is a container type whose value_type is equal to the container type -// itself. An example for a recursive container type is -// boost::filesystem::path, whose iterator has a value_type that is equal to -// boost::filesystem::path. -template -struct IsRecursiveContainer : public IsRecursiveContainerImpl::type {}; - -// EnableIf::type is void when 'Cond' is true, and -// undefined when 'Cond' is false. To use SFINAE to make a function -// overload only apply when a particular expression is true, add -// "typename EnableIf::type* = 0" as the last parameter. -template struct EnableIf; -template<> struct EnableIf { typedef void type; }; // NOLINT - -// Utilities for native arrays. - -// ArrayEq() compares two k-dimensional native arrays using the -// elements' operator==, where k can be any integer >= 0. When k is -// 0, ArrayEq() degenerates into comparing a single pair of values. - -template -bool ArrayEq(const T* lhs, size_t size, const U* rhs); - -// This generic version is used when k is 0. -template -inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } - -// This overload is used when k >= 1. -template -inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { - return internal::ArrayEq(lhs, N, rhs); -} - -// This helper reduces code bloat. If we instead put its logic inside -// the previous ArrayEq() function, arrays with different sizes would -// lead to different copies of the template code. -template -bool ArrayEq(const T* lhs, size_t size, const U* rhs) { - for (size_t i = 0; i != size; i++) { - if (!internal::ArrayEq(lhs[i], rhs[i])) - return false; - } - return true; -} - -// Finds the first element in the iterator range [begin, end) that -// equals elem. Element may be a native array type itself. -template -Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { - for (Iter it = begin; it != end; ++it) { - if (internal::ArrayEq(*it, elem)) - return it; - } - return end; -} - -// CopyArray() copies a k-dimensional native array using the elements' -// operator=, where k can be any integer >= 0. When k is 0, -// CopyArray() degenerates into copying a single value. - -template -void CopyArray(const T* from, size_t size, U* to); - -// This generic version is used when k is 0. -template -inline void CopyArray(const T& from, U* to) { *to = from; } - -// This overload is used when k >= 1. -template -inline void CopyArray(const T(&from)[N], U(*to)[N]) { - internal::CopyArray(from, N, *to); -} - -// This helper reduces code bloat. If we instead put its logic inside -// the previous CopyArray() function, arrays with different sizes -// would lead to different copies of the template code. -template -void CopyArray(const T* from, size_t size, U* to) { - for (size_t i = 0; i != size; i++) { - internal::CopyArray(from[i], to + i); - } -} - -// The relation between an NativeArray object (see below) and the -// native array it represents. -// We use 2 different structs to allow non-copyable types to be used, as long -// as RelationToSourceReference() is passed. -struct RelationToSourceReference {}; -struct RelationToSourceCopy {}; - -// Adapts a native array to a read-only STL-style container. Instead -// of the complete STL container concept, this adaptor only implements -// members useful for Google Mock's container matchers. New members -// should be added as needed. To simplify the implementation, we only -// support Element being a raw type (i.e. having no top-level const or -// reference modifier). It's the client's responsibility to satisfy -// this requirement. Element can be an array type itself (hence -// multi-dimensional arrays are supported). -template -class NativeArray { - public: - // STL-style container typedefs. - typedef Element value_type; - typedef Element* iterator; - typedef const Element* const_iterator; - - // Constructs from a native array. References the source. - NativeArray(const Element* array, size_t count, RelationToSourceReference) { - InitRef(array, count); - } - - // Constructs from a native array. Copies the source. - NativeArray(const Element* array, size_t count, RelationToSourceCopy) { - InitCopy(array, count); - } - - // Copy constructor. - NativeArray(const NativeArray& rhs) { - (this->*rhs.clone_)(rhs.array_, rhs.size_); - } - - ~NativeArray() { - if (clone_ != &NativeArray::InitRef) - delete[] array_; - } - - // STL-style container methods. - size_t size() const { return size_; } - const_iterator begin() const { return array_; } - const_iterator end() const { return array_ + size_; } - bool operator==(const NativeArray& rhs) const { - return size() == rhs.size() && - ArrayEq(begin(), size(), rhs.begin()); - } - - private: - enum { - kCheckTypeIsNotConstOrAReference = StaticAssertTypeEqHelper< - Element, GTEST_REMOVE_REFERENCE_AND_CONST_(Element)>::value - }; - - // Initializes this object with a copy of the input. - void InitCopy(const Element* array, size_t a_size) { - Element* const copy = new Element[a_size]; - CopyArray(array, a_size, copy); - array_ = copy; - size_ = a_size; - clone_ = &NativeArray::InitCopy; - } - - // Initializes this object with a reference of the input. - void InitRef(const Element* array, size_t a_size) { - array_ = array; - size_ = a_size; - clone_ = &NativeArray::InitRef; - } - - const Element* array_; - size_t size_; - void (NativeArray::*clone_)(const Element*, size_t); - - GTEST_DISALLOW_ASSIGN_(NativeArray); -}; - -// Backport of std::index_sequence. -template -struct IndexSequence { - using type = IndexSequence; -}; - -// Double the IndexSequence, and one if plus_one is true. -template -struct DoubleSequence; -template -struct DoubleSequence, sizeofT> { - using type = IndexSequence; -}; -template -struct DoubleSequence, sizeofT> { - using type = IndexSequence; -}; - -// Backport of std::make_index_sequence. -// It uses O(ln(N)) instantiation depth. -template -struct MakeIndexSequence - : DoubleSequence::type, - N / 2>::type {}; - -template <> -struct MakeIndexSequence<0> : IndexSequence<> {}; - -// FIXME: This implementation of ElemFromList is O(1) in instantiation depth, -// but it is O(N^2) in total instantiations. Not sure if this is the best -// tradeoff, as it will make it somewhat slow to compile. -template -struct ElemFromListImpl {}; - -template -struct ElemFromListImpl { - using type = T; -}; - -// Get the Nth element from T... -// It uses O(1) instantiation depth. -template -struct ElemFromList; - -template -struct ElemFromList, T...> - : ElemFromListImpl... {}; - -template -class FlatTuple; - -template -struct FlatTupleElemBase; - -template -struct FlatTupleElemBase, I> { - using value_type = - typename ElemFromList::type, - T...>::type; - FlatTupleElemBase() = default; - explicit FlatTupleElemBase(value_type t) : value(std::move(t)) {} - value_type value; -}; - -template -struct FlatTupleBase; - -template -struct FlatTupleBase, IndexSequence> - : FlatTupleElemBase, Idx>... { - using Indices = IndexSequence; - FlatTupleBase() = default; - explicit FlatTupleBase(T... t) - : FlatTupleElemBase, Idx>(std::move(t))... {} -}; - -// Analog to std::tuple but with different tradeoffs. -// This class minimizes the template instantiation depth, thus allowing more -// elements that std::tuple would. std::tuple has been seen to require an -// instantiation depth of more than 10x the number of elements in some -// implementations. -// FlatTuple and ElemFromList are not recursive and have a fixed depth -// regardless of T... -// MakeIndexSequence, on the other hand, it is recursive but with an -// instantiation depth of O(ln(N)). -template -class FlatTuple - : private FlatTupleBase, - typename MakeIndexSequence::type> { - using Indices = typename FlatTuple::FlatTupleBase::Indices; - - public: - FlatTuple() = default; - explicit FlatTuple(T... t) : FlatTuple::FlatTupleBase(std::move(t)...) {} - - template - const typename ElemFromList::type& Get() const { - return static_cast*>(this)->value; - } - - template - typename ElemFromList::type& Get() { - return static_cast*>(this)->value; - } -}; - -// Utility functions to be called with static_assert to induce deprecation -// warnings. -GTEST_INTERNAL_DEPRECATED( - "INSTANTIATE_TEST_CASE_P is deprecated, please use " - "INSTANTIATE_TEST_SUITE_P") -constexpr bool InstantiateTestCase_P_IsDeprecated() { return true; } - -GTEST_INTERNAL_DEPRECATED( - "TYPED_TEST_CASE_P is deprecated, please use " - "TYPED_TEST_SUITE_P") -constexpr bool TypedTestCase_P_IsDeprecated() { return true; } - -GTEST_INTERNAL_DEPRECATED( - "TYPED_TEST_CASE is deprecated, please use " - "TYPED_TEST_SUITE") -constexpr bool TypedTestCaseIsDeprecated() { return true; } - -GTEST_INTERNAL_DEPRECATED( - "REGISTER_TYPED_TEST_CASE_P is deprecated, please use " - "REGISTER_TYPED_TEST_SUITE_P") -constexpr bool RegisterTypedTestCase_P_IsDeprecated() { return true; } - -GTEST_INTERNAL_DEPRECATED( - "INSTANTIATE_TYPED_TEST_CASE_P is deprecated, please use " - "INSTANTIATE_TYPED_TEST_SUITE_P") -constexpr bool InstantiateTypedTestCase_P_IsDeprecated() { return true; } - -} // namespace internal -} // namespace testing - -#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ - ::testing::internal::AssertHelper(result_type, file, line, message) \ - = ::testing::Message() - -#define GTEST_MESSAGE_(message, result_type) \ - GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) - -#define GTEST_FATAL_FAILURE_(message) \ - return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) - -#define GTEST_NONFATAL_FAILURE_(message) \ - GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) - -#define GTEST_SUCCESS_(message) \ - GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) - -#define GTEST_SKIP_(message) \ - return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip) - -// Suppress MSVC warning 4072 (unreachable code) for the code following -// statement if it returns or throws (or doesn't return or throw in some -// situations). -#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ - if (::testing::internal::AlwaysTrue()) { statement; } - -#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::ConstCharPtr gtest_msg = "") { \ - bool gtest_caught_expected = false; \ - try { \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - } \ - catch (expected_exception const&) { \ - gtest_caught_expected = true; \ - } \ - catch (...) { \ - gtest_msg.value = \ - "Expected: " #statement " throws an exception of type " \ - #expected_exception ".\n Actual: it throws a different type."; \ - goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ - } \ - if (!gtest_caught_expected) { \ - gtest_msg.value = \ - "Expected: " #statement " throws an exception of type " \ - #expected_exception ".\n Actual: it throws nothing."; \ - goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ - } \ - } else \ - GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ - fail(gtest_msg.value) - -#define GTEST_TEST_NO_THROW_(statement, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - try { \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - } \ - catch (...) { \ - goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ - } \ - } else \ - GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ - fail("Expected: " #statement " doesn't throw an exception.\n" \ - " Actual: it throws.") - -#define GTEST_TEST_ANY_THROW_(statement, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - bool gtest_caught_any = false; \ - try { \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - } \ - catch (...) { \ - gtest_caught_any = true; \ - } \ - if (!gtest_caught_any) { \ - goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ - } \ - } else \ - GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ - fail("Expected: " #statement " throws an exception.\n" \ - " Actual: it doesn't.") - - -// Implements Boolean test assertions such as EXPECT_TRUE. expression can be -// either a boolean expression or an AssertionResult. text is a textual -// represenation of expression as it was passed into the EXPECT_TRUE. -#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const ::testing::AssertionResult gtest_ar_ = \ - ::testing::AssertionResult(expression)) \ - ; \ - else \ - fail(::testing::internal::GetBoolAssertionFailureMessage(\ - gtest_ar_, text, #actual, #expected).c_str()) - -#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ - goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ - } \ - } else \ - GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ - fail("Expected: " #statement " doesn't generate new fatal " \ - "failures in the current thread.\n" \ - " Actual: it does.") - -// Expands to the name of the class that implements the given test. -#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ - test_suite_name##_##test_name##_Test - -// Helper macro for defining tests. -#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id) \ - class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ - : public parent_class { \ - public: \ - GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \ - \ - private: \ - virtual void TestBody(); \ - static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_; \ - GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \ - test_name)); \ - }; \ - \ - ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \ - test_name)::test_info_ = \ - ::testing::internal::MakeAndRegisterTestInfo( \ - #test_suite_name, #test_name, nullptr, nullptr, \ - ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \ - ::testing::internal::SuiteApiResolver< \ - parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__), \ - ::testing::internal::SuiteApiResolver< \ - parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__), \ - new ::testing::internal::TestFactoryImpl); \ - void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file defines the public API for death tests. It is -// #included by gtest.h so a user doesn't need to include this -// directly. -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ -#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ - -// Copyright 2005, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This header file defines internal utilities needed for implementing -// death tests. They are subject to change without notice. -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ - -// Copyright 2007, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The Google C++ Testing and Mocking Framework (Google Test) -// -// This file implements just enough of the matcher interface to allow -// EXPECT_DEATH and friends to accept a matcher argument. - -// IWYU pragma: private, include "testing/base/public/gunit.h" -// IWYU pragma: friend third_party/googletest/googlemock/.* -// IWYU pragma: friend third_party/googletest/googletest/.* - -#ifndef GTEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ -#define GTEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ - -#include -#include -#include - -// Copyright 2007, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -// Google Test - The Google C++ Testing and Mocking Framework -// -// This file implements a universal value printer that can print a -// value of any type T: -// -// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); -// -// A user can teach this function how to print a class type T by -// defining either operator<<() or PrintTo() in the namespace that -// defines T. More specifically, the FIRST defined function in the -// following list will be used (assuming T is defined in namespace -// foo): -// -// 1. foo::PrintTo(const T&, ostream*) -// 2. operator<<(ostream&, const T&) defined in either foo or the -// global namespace. -// -// However if T is an STL-style container then it is printed element-wise -// unless foo::PrintTo(const T&, ostream*) is defined. Note that -// operator<<() is ignored for container types. -// -// If none of the above is defined, it will print the debug string of -// the value if it is a protocol buffer, or print the raw bytes in the -// value otherwise. -// -// To aid debugging: when T is a reference type, the address of the -// value is also printed; when T is a (const) char pointer, both the -// pointer value and the NUL-terminated string it points to are -// printed. -// -// We also provide some convenient wrappers: -// -// // Prints a value to a string. For a (const or not) char -// // pointer, the NUL-terminated string (but not the pointer) is -// // printed. -// std::string ::testing::PrintToString(const T& value); -// -// // Prints a value tersely: for a reference type, the referenced -// // value (but not the address) is printed; for a (const or not) char -// // pointer, the NUL-terminated string (but not the pointer) is -// // printed. -// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); -// -// // Prints value using the type inferred by the compiler. The difference -// // from UniversalTersePrint() is that this function prints both the -// // pointer and the NUL-terminated string for a (const or not) char pointer. -// void ::testing::internal::UniversalPrint(const T& value, ostream*); -// -// // Prints the fields of a tuple tersely to a string vector, one -// // element for each field. Tuple support must be enabled in -// // gtest-port.h. -// std::vector UniversalTersePrintTupleFieldsToStrings( -// const Tuple& value); -// -// Known limitation: -// -// The print primitives print the elements of an STL-style container -// using the compiler-inferred type of *iter where iter is a -// const_iterator of the container. When const_iterator is an input -// iterator but not a forward iterator, this inferred type may not -// match value_type, and the print output may be incorrect. In -// practice, this is rarely a problem as for most containers -// const_iterator is a forward iterator. We'll fix this if there's an -// actual need for it. Note that this fix cannot rely on value_type -// being defined as many user-defined container types don't have -// value_type. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ -#define GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ - -#include -#include // NOLINT -#include -#include -#include -#include -#include -#include - -#if GTEST_HAS_ABSL -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" -#include "absl/types/variant.h" -#endif // GTEST_HAS_ABSL - -namespace testing { - -// Definitions in the 'internal' and 'internal2' name spaces are -// subject to change without notice. DO NOT USE THEM IN USER CODE! -namespace internal2 { - -// Prints the given number of bytes in the given object to the given -// ostream. -GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, - size_t count, - ::std::ostream* os); - -// For selecting which printer to use when a given type has neither << -// nor PrintTo(). -enum TypeKind { - kProtobuf, // a protobuf type - kConvertibleToInteger, // a type implicitly convertible to BiggestInt - // (e.g. a named or unnamed enum type) -#if GTEST_HAS_ABSL - kConvertibleToStringView, // a type implicitly convertible to - // absl::string_view -#endif - kOtherType // anything else -}; - -// TypeWithoutFormatter::PrintValue(value, os) is called -// by the universal printer to print a value of type T when neither -// operator<< nor PrintTo() is defined for T, where kTypeKind is the -// "kind" of T as defined by enum TypeKind. -template -class TypeWithoutFormatter { - public: - // This default version is called when kTypeKind is kOtherType. - static void PrintValue(const T& value, ::std::ostream* os) { - PrintBytesInObjectTo( - static_cast( - reinterpret_cast(std::addressof(value))), - sizeof(value), os); - } -}; - -// We print a protobuf using its ShortDebugString() when the string -// doesn't exceed this many characters; otherwise we print it using -// DebugString() for better readability. -const size_t kProtobufOneLinerMaxLength = 50; - -template -class TypeWithoutFormatter { - public: - static void PrintValue(const T& value, ::std::ostream* os) { - std::string pretty_str = value.ShortDebugString(); - if (pretty_str.length() > kProtobufOneLinerMaxLength) { - pretty_str = "\n" + value.DebugString(); - } - *os << ("<" + pretty_str + ">"); - } -}; - -template -class TypeWithoutFormatter { - public: - // Since T has no << operator or PrintTo() but can be implicitly - // converted to BiggestInt, we print it as a BiggestInt. - // - // Most likely T is an enum type (either named or unnamed), in which - // case printing it as an integer is the desired behavior. In case - // T is not an enum, printing it as an integer is the best we can do - // given that it has no user-defined printer. - static void PrintValue(const T& value, ::std::ostream* os) { - const internal::BiggestInt kBigInt = value; - *os << kBigInt; - } -}; - -#if GTEST_HAS_ABSL -template -class TypeWithoutFormatter { - public: - // Since T has neither operator<< nor PrintTo() but can be implicitly - // converted to absl::string_view, we print it as a absl::string_view. - // - // Note: the implementation is further below, as it depends on - // internal::PrintTo symbol which is defined later in the file. - static void PrintValue(const T& value, ::std::ostream* os); -}; -#endif - -// Prints the given value to the given ostream. If the value is a -// protocol message, its debug string is printed; if it's an enum or -// of a type implicitly convertible to BiggestInt, it's printed as an -// integer; otherwise the bytes in the value are printed. This is -// what UniversalPrinter::Print() does when it knows nothing about -// type T and T has neither << operator nor PrintTo(). -// -// A user can override this behavior for a class type Foo by defining -// a << operator in the namespace where Foo is defined. -// -// We put this operator in namespace 'internal2' instead of 'internal' -// to simplify the implementation, as much code in 'internal' needs to -// use << in STL, which would conflict with our own << were it defined -// in 'internal'. -// -// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If -// we define it to take an std::ostream instead, we'll get an -// "ambiguous overloads" compiler error when trying to print a type -// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether -// operator<<(std::ostream&, const T&) or -// operator<<(std::basic_stream, const Foo&) is more -// specific. -template -::std::basic_ostream& operator<<( - ::std::basic_ostream& os, const T& x) { - TypeWithoutFormatter::value - ? kProtobuf - : std::is_convertible< - const T&, internal::BiggestInt>::value - ? kConvertibleToInteger - : -#if GTEST_HAS_ABSL - std::is_convertible< - const T&, absl::string_view>::value - ? kConvertibleToStringView - : -#endif - kOtherType)>::PrintValue(x, &os); - return os; -} - -} // namespace internal2 -} // namespace testing - -// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up -// magic needed for implementing UniversalPrinter won't work. -namespace testing_internal { - -// Used to print a value that is not an STL-style container when the -// user doesn't define PrintTo() for it. -template -void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { - // With the following statement, during unqualified name lookup, - // testing::internal2::operator<< appears as if it was declared in - // the nearest enclosing namespace that contains both - // ::testing_internal and ::testing::internal2, i.e. the global - // namespace. For more details, refer to the C++ Standard section - // 7.3.4-1 [namespace.udir]. This allows us to fall back onto - // testing::internal2::operator<< in case T doesn't come with a << - // operator. - // - // We cannot write 'using ::testing::internal2::operator<<;', which - // gcc 3.3 fails to compile due to a compiler bug. - using namespace ::testing::internal2; // NOLINT - - // Assuming T is defined in namespace foo, in the next statement, - // the compiler will consider all of: - // - // 1. foo::operator<< (thanks to Koenig look-up), - // 2. ::operator<< (as the current namespace is enclosed in ::), - // 3. testing::internal2::operator<< (thanks to the using statement above). - // - // The operator<< whose type matches T best will be picked. - // - // We deliberately allow #2 to be a candidate, as sometimes it's - // impossible to define #1 (e.g. when foo is ::std, defining - // anything in it is undefined behavior unless you are a compiler - // vendor.). - *os << value; -} - -} // namespace testing_internal - -namespace testing { -namespace internal { - -// FormatForComparison::Format(value) formats a -// value of type ToPrint that is an operand of a comparison assertion -// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in -// the comparison, and is used to help determine the best way to -// format the value. In particular, when the value is a C string -// (char pointer) and the other operand is an STL string object, we -// want to format the C string as a string, since we know it is -// compared by value with the string object. If the value is a char -// pointer but the other operand is not an STL string object, we don't -// know whether the pointer is supposed to point to a NUL-terminated -// string, and thus want to print it as a pointer to be safe. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - -// The default case. -template -class FormatForComparison { - public: - static ::std::string Format(const ToPrint& value) { - return ::testing::PrintToString(value); - } -}; - -// Array. -template -class FormatForComparison { - public: - static ::std::string Format(const ToPrint* value) { - return FormatForComparison::Format(value); - } -}; - -// By default, print C string as pointers to be safe, as we don't know -// whether they actually point to a NUL-terminated string. - -#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ - template \ - class FormatForComparison { \ - public: \ - static ::std::string Format(CharType* value) { \ - return ::testing::PrintToString(static_cast(value)); \ - } \ - } - -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); -GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); - -#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ - -// If a C string is compared with an STL string object, we know it's meant -// to point to a NUL-terminated string, and thus can print it as a string. - -#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ - template <> \ - class FormatForComparison { \ - public: \ - static ::std::string Format(CharType* value) { \ - return ::testing::PrintToString(value); \ - } \ - } - -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); - -#if GTEST_HAS_STD_WSTRING -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); -GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); -#endif - -#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ - -// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) -// operand to be used in a failure message. The type (but not value) -// of the other operand may affect the format. This allows us to -// print a char* as a raw pointer when it is compared against another -// char* or void*, and print it as a C string when it is compared -// against an std::string object, for example. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -template -std::string FormatForComparisonFailureMessage( - const T1& value, const T2& /* other_operand */) { - return FormatForComparison::Format(value); -} - -// UniversalPrinter::Print(value, ostream_ptr) prints the given -// value to the given ostream. The caller must ensure that -// 'ostream_ptr' is not NULL, or the behavior is undefined. -// -// We define UniversalPrinter as a class template (as opposed to a -// function template), as we need to partially specialize it for -// reference types, which cannot be done with function templates. -template -class UniversalPrinter; - -template -void UniversalPrint(const T& value, ::std::ostream* os); - -enum DefaultPrinterType { - kPrintContainer, - kPrintPointer, - kPrintFunctionPointer, - kPrintOther, -}; -template struct WrapPrinterType {}; - -// Used to print an STL-style container when the user doesn't define -// a PrintTo() for it. -template -void DefaultPrintTo(WrapPrinterType /* dummy */, - const C& container, ::std::ostream* os) { - const size_t kMaxCount = 32; // The maximum number of elements to print. - *os << '{'; - size_t count = 0; - for (typename C::const_iterator it = container.begin(); - it != container.end(); ++it, ++count) { - if (count > 0) { - *os << ','; - if (count == kMaxCount) { // Enough has been printed. - *os << " ..."; - break; - } - } - *os << ' '; - // We cannot call PrintTo(*it, os) here as PrintTo() doesn't - // handle *it being a native array. - internal::UniversalPrint(*it, os); - } - - if (count > 0) { - *os << ' '; - } - *os << '}'; -} - -// Used to print a pointer that is neither a char pointer nor a member -// pointer, when the user doesn't define PrintTo() for it. (A member -// variable pointer or member function pointer doesn't really point to -// a location in the address space. Their representation is -// implementation-defined. Therefore they will be printed as raw -// bytes.) -template -void DefaultPrintTo(WrapPrinterType /* dummy */, - T* p, ::std::ostream* os) { - if (p == nullptr) { - *os << "NULL"; - } else { - // T is not a function type. We just call << to print p, - // relying on ADL to pick up user-defined << for their pointer - // types, if any. - *os << p; - } -} -template -void DefaultPrintTo(WrapPrinterType /* dummy */, - T* p, ::std::ostream* os) { - if (p == nullptr) { - *os << "NULL"; - } else { - // T is a function type, so '*os << p' doesn't do what we want - // (it just prints p as bool). We want to print p as a const - // void*. - *os << reinterpret_cast(p); - } -} - -// Used to print a non-container, non-pointer value when the user -// doesn't define PrintTo() for it. -template -void DefaultPrintTo(WrapPrinterType /* dummy */, - const T& value, ::std::ostream* os) { - ::testing_internal::DefaultPrintNonContainerTo(value, os); -} - -// Prints the given value using the << operator if it has one; -// otherwise prints the bytes in it. This is what -// UniversalPrinter::Print() does when PrintTo() is not specialized -// or overloaded for type T. -// -// A user can override this behavior for a class type Foo by defining -// an overload of PrintTo() in the namespace where Foo is defined. We -// give the user this option as sometimes defining a << operator for -// Foo is not desirable (e.g. the coding style may prevent doing it, -// or there is already a << operator but it doesn't do what the user -// wants). -template -void PrintTo(const T& value, ::std::ostream* os) { - // DefaultPrintTo() is overloaded. The type of its first argument - // determines which version will be picked. - // - // Note that we check for container types here, prior to we check - // for protocol message types in our operator<<. The rationale is: - // - // For protocol messages, we want to give people a chance to - // override Google Mock's format by defining a PrintTo() or - // operator<<. For STL containers, other formats can be - // incompatible with Google Mock's format for the container - // elements; therefore we check for container types here to ensure - // that our format is used. - // - // Note that MSVC and clang-cl do allow an implicit conversion from - // pointer-to-function to pointer-to-object, but clang-cl warns on it. - // So don't use ImplicitlyConvertible if it can be helped since it will - // cause this warning, and use a separate overload of DefaultPrintTo for - // function pointers so that the `*os << p` in the object pointer overload - // doesn't cause that warning either. - DefaultPrintTo( - WrapPrinterType < - (sizeof(IsContainerTest(0)) == sizeof(IsContainer)) && - !IsRecursiveContainer::value - ? kPrintContainer - : !std::is_pointer::value - ? kPrintOther - : std::is_function::type>::value - ? kPrintFunctionPointer - : kPrintPointer > (), - value, os); -} - -// The following list of PrintTo() overloads tells -// UniversalPrinter::Print() how to print standard types (built-in -// types, strings, plain arrays, and pointers). - -// Overloads for various char types. -GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); -GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); -inline void PrintTo(char c, ::std::ostream* os) { - // When printing a plain char, we always treat it as unsigned. This - // way, the output won't be affected by whether the compiler thinks - // char is signed or not. - PrintTo(static_cast(c), os); -} - -// Overloads for other simple built-in types. -inline void PrintTo(bool x, ::std::ostream* os) { - *os << (x ? "true" : "false"); -} - -// Overload for wchar_t type. -// Prints a wchar_t as a symbol if it is printable or as its internal -// code otherwise and also as its decimal code (except for L'\0'). -// The L'\0' char is printed as "L'\\0'". The decimal code is printed -// as signed integer when wchar_t is implemented by the compiler -// as a signed type and is printed as an unsigned integer when wchar_t -// is implemented as an unsigned type. -GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); - -// Overloads for C strings. -GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); -inline void PrintTo(char* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} - -// signed/unsigned char is often used for representing binary data, so -// we print pointers to it as void* to be safe. -inline void PrintTo(const signed char* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} -inline void PrintTo(signed char* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} -inline void PrintTo(const unsigned char* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} -inline void PrintTo(unsigned char* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} - -// MSVC can be configured to define wchar_t as a typedef of unsigned -// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native -// type. When wchar_t is a typedef, defining an overload for const -// wchar_t* would cause unsigned short* be printed as a wide string, -// possibly causing invalid memory accesses. -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) -// Overloads for wide C strings -GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); -inline void PrintTo(wchar_t* s, ::std::ostream* os) { - PrintTo(ImplicitCast_(s), os); -} -#endif - -// Overload for C arrays. Multi-dimensional arrays are printed -// properly. - -// Prints the given number of elements in an array, without printing -// the curly braces. -template -void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { - UniversalPrint(a[0], os); - for (size_t i = 1; i != count; i++) { - *os << ", "; - UniversalPrint(a[i], os); - } -} - -// Overloads for ::std::string. -GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os); -inline void PrintTo(const ::std::string& s, ::std::ostream* os) { - PrintStringTo(s, os); -} - -// Overloads for ::std::wstring. -#if GTEST_HAS_STD_WSTRING -GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); -inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { - PrintWideStringTo(s, os); -} -#endif // GTEST_HAS_STD_WSTRING - -#if GTEST_HAS_ABSL -// Overload for absl::string_view. -inline void PrintTo(absl::string_view sp, ::std::ostream* os) { - PrintTo(::std::string(sp), os); -} -#endif // GTEST_HAS_ABSL - -inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; } - -template -void PrintTo(std::reference_wrapper ref, ::std::ostream* os) { - UniversalPrinter::Print(ref.get(), os); -} - -// Helper function for printing a tuple. T must be instantiated with -// a tuple type. -template -void PrintTupleTo(const T&, std::integral_constant, - ::std::ostream*) {} - -template -void PrintTupleTo(const T& t, std::integral_constant, - ::std::ostream* os) { - PrintTupleTo(t, std::integral_constant(), os); - GTEST_INTENTIONAL_CONST_COND_PUSH_() - if (I > 1) { - GTEST_INTENTIONAL_CONST_COND_POP_() - *os << ", "; - } - UniversalPrinter::type>::Print( - std::get(t), os); -} - -template -void PrintTo(const ::std::tuple& t, ::std::ostream* os) { - *os << "("; - PrintTupleTo(t, std::integral_constant(), os); - *os << ")"; -} - -// Overload for std::pair. -template -void PrintTo(const ::std::pair& value, ::std::ostream* os) { - *os << '('; - // We cannot use UniversalPrint(value.first, os) here, as T1 may be - // a reference type. The same for printing value.second. - UniversalPrinter::Print(value.first, os); - *os << ", "; - UniversalPrinter::Print(value.second, os); - *os << ')'; -} - -// Implements printing a non-reference type T by letting the compiler -// pick the right overload of PrintTo() for T. -template -class UniversalPrinter { - public: - // MSVC warns about adding const to a function type, so we want to - // disable the warning. - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) - - // Note: we deliberately don't call this PrintTo(), as that name - // conflicts with ::testing::internal::PrintTo in the body of the - // function. - static void Print(const T& value, ::std::ostream* os) { - // By default, ::testing::internal::PrintTo() is used for printing - // the value. - // - // Thanks to Koenig look-up, if T is a class and has its own - // PrintTo() function defined in its namespace, that function will - // be visible here. Since it is more specific than the generic ones - // in ::testing::internal, it will be picked by the compiler in the - // following statement - exactly what we want. - PrintTo(value, os); - } - - GTEST_DISABLE_MSC_WARNINGS_POP_() -}; - -#if GTEST_HAS_ABSL - -// Printer for absl::optional - -template -class UniversalPrinter<::absl::optional> { - public: - static void Print(const ::absl::optional& value, ::std::ostream* os) { - *os << '('; - if (!value) { - *os << "nullopt"; - } else { - UniversalPrint(*value, os); - } - *os << ')'; - } -}; - -// Printer for absl::variant - -template -class UniversalPrinter<::absl::variant> { - public: - static void Print(const ::absl::variant& value, ::std::ostream* os) { - *os << '('; - absl::visit(Visitor{os}, value); - *os << ')'; - } - - private: - struct Visitor { - template - void operator()(const U& u) const { - *os << "'" << GetTypeName() << "' with value "; - UniversalPrint(u, os); - } - ::std::ostream* os; - }; -}; - -#endif // GTEST_HAS_ABSL - -// UniversalPrintArray(begin, len, os) prints an array of 'len' -// elements, starting at address 'begin'. -template -void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { - if (len == 0) { - *os << "{}"; - } else { - *os << "{ "; - const size_t kThreshold = 18; - const size_t kChunkSize = 8; - // If the array has more than kThreshold elements, we'll have to - // omit some details by printing only the first and the last - // kChunkSize elements. - if (len <= kThreshold) { - PrintRawArrayTo(begin, len, os); - } else { - PrintRawArrayTo(begin, kChunkSize, os); - *os << ", ..., "; - PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); - } - *os << " }"; - } -} -// This overload prints a (const) char array compactly. -GTEST_API_ void UniversalPrintArray( - const char* begin, size_t len, ::std::ostream* os); - -// This overload prints a (const) wchar_t array compactly. -GTEST_API_ void UniversalPrintArray( - const wchar_t* begin, size_t len, ::std::ostream* os); - -// Implements printing an array type T[N]. -template -class UniversalPrinter { - public: - // Prints the given array, omitting some elements when there are too - // many. - static void Print(const T (&a)[N], ::std::ostream* os) { - UniversalPrintArray(a, N, os); - } -}; - -// Implements printing a reference type T&. -template -class UniversalPrinter { - public: - // MSVC warns about adding const to a function type, so we want to - // disable the warning. - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180) - - static void Print(const T& value, ::std::ostream* os) { - // Prints the address of the value. We use reinterpret_cast here - // as static_cast doesn't compile when T is a function type. - *os << "@" << reinterpret_cast(&value) << " "; - - // Then prints the value itself. - UniversalPrint(value, os); - } - - GTEST_DISABLE_MSC_WARNINGS_POP_() -}; - -// Prints a value tersely: for a reference type, the referenced value -// (but not the address) is printed; for a (const) char pointer, the -// NUL-terminated string (but not the pointer) is printed. - -template -class UniversalTersePrinter { - public: - static void Print(const T& value, ::std::ostream* os) { - UniversalPrint(value, os); - } -}; -template -class UniversalTersePrinter { - public: - static void Print(const T& value, ::std::ostream* os) { - UniversalPrint(value, os); - } -}; -template -class UniversalTersePrinter { - public: - static void Print(const T (&value)[N], ::std::ostream* os) { - UniversalPrinter::Print(value, os); - } -}; -template <> -class UniversalTersePrinter { - public: - static void Print(const char* str, ::std::ostream* os) { - if (str == nullptr) { - *os << "NULL"; - } else { - UniversalPrint(std::string(str), os); - } - } -}; -template <> -class UniversalTersePrinter { - public: - static void Print(char* str, ::std::ostream* os) { - UniversalTersePrinter::Print(str, os); - } -}; - -#if GTEST_HAS_STD_WSTRING -template <> -class UniversalTersePrinter { - public: - static void Print(const wchar_t* str, ::std::ostream* os) { - if (str == nullptr) { - *os << "NULL"; - } else { - UniversalPrint(::std::wstring(str), os); - } - } -}; -#endif - -template <> -class UniversalTersePrinter { - public: - static void Print(wchar_t* str, ::std::ostream* os) { - UniversalTersePrinter::Print(str, os); - } -}; - -template -void UniversalTersePrint(const T& value, ::std::ostream* os) { - UniversalTersePrinter::Print(value, os); -} - -// Prints a value using the type inferred by the compiler. The -// difference between this and UniversalTersePrint() is that for a -// (const) char pointer, this prints both the pointer and the -// NUL-terminated string. -template -void UniversalPrint(const T& value, ::std::ostream* os) { - // A workarond for the bug in VC++ 7.1 that prevents us from instantiating - // UniversalPrinter with T directly. - typedef T T1; - UniversalPrinter::Print(value, os); -} - -typedef ::std::vector< ::std::string> Strings; - - // Tersely prints the first N fields of a tuple to a string vector, - // one element for each field. -template -void TersePrintPrefixToStrings(const Tuple&, std::integral_constant, - Strings*) {} -template -void TersePrintPrefixToStrings(const Tuple& t, - std::integral_constant, - Strings* strings) { - TersePrintPrefixToStrings(t, std::integral_constant(), - strings); - ::std::stringstream ss; - UniversalTersePrint(std::get(t), &ss); - strings->push_back(ss.str()); -} - -// Prints the fields of a tuple tersely to a string vector, one -// element for each field. See the comment before -// UniversalTersePrint() for how we define "tersely". -template -Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { - Strings result; - TersePrintPrefixToStrings( - value, std::integral_constant::value>(), - &result); - return result; -} - -} // namespace internal - -#if GTEST_HAS_ABSL -namespace internal2 { -template -void TypeWithoutFormatter::PrintValue( - const T& value, ::std::ostream* os) { - internal::PrintTo(absl::string_view(value), os); -} -} // namespace internal2 -#endif - -template -::std::string PrintToString(const T& value) { - ::std::stringstream ss; - internal::UniversalTersePrinter::Print(value, &ss); - return ss.str(); -} - -} // namespace testing - -// Include any custom printer added by the local installation. -// We must include this header at the end to make sure it can use the -// declarations from this file. -// Copyright 2015, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// This file provides an injection point for custom printers in a local -// installation of gTest. -// It will be included from gtest-printers.h and the overrides in this file -// will be visible to everyone. -// -// Injection point for custom user configurations. See README for details -// -// ** Custom implementation starts here ** - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_ - -#endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ - -// MSVC warning C5046 is new as of VS2017 version 15.8. -#if defined(_MSC_VER) && _MSC_VER >= 1915 -#define GTEST_MAYBE_5046_ 5046 -#else -#define GTEST_MAYBE_5046_ -#endif - -GTEST_DISABLE_MSC_WARNINGS_PUSH_( - 4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by - clients of class B */ - /* Symbol involving type with internal linkage not defined */) - -namespace testing { - -// To implement a matcher Foo for type T, define: -// 1. a class FooMatcherImpl that implements the -// MatcherInterface interface, and -// 2. a factory function that creates a Matcher object from a -// FooMatcherImpl*. -// -// The two-level delegation design makes it possible to allow a user -// to write "v" instead of "Eq(v)" where a Matcher is expected, which -// is impossible if we pass matchers by pointers. It also eases -// ownership management as Matcher objects can now be copied like -// plain values. - -// MatchResultListener is an abstract class. Its << operator can be -// used by a matcher to explain why a value matches or doesn't match. -// -class MatchResultListener { - public: - // Creates a listener object with the given underlying ostream. The - // listener does not own the ostream, and does not dereference it - // in the constructor or destructor. - explicit MatchResultListener(::std::ostream* os) : stream_(os) {} - virtual ~MatchResultListener() = 0; // Makes this class abstract. - - // Streams x to the underlying ostream; does nothing if the ostream - // is NULL. - template - MatchResultListener& operator<<(const T& x) { - if (stream_ != nullptr) *stream_ << x; - return *this; - } - - // Returns the underlying ostream. - ::std::ostream* stream() { return stream_; } - - // Returns true iff the listener is interested in an explanation of - // the match result. A matcher's MatchAndExplain() method can use - // this information to avoid generating the explanation when no one - // intends to hear it. - bool IsInterested() const { return stream_ != nullptr; } - - private: - ::std::ostream* const stream_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); -}; - -inline MatchResultListener::~MatchResultListener() { -} - -// An instance of a subclass of this knows how to describe itself as a -// matcher. -class MatcherDescriberInterface { - public: - virtual ~MatcherDescriberInterface() {} - - // Describes this matcher to an ostream. The function should print - // a verb phrase that describes the property a value matching this - // matcher should have. The subject of the verb phrase is the value - // being matched. For example, the DescribeTo() method of the Gt(7) - // matcher prints "is greater than 7". - virtual void DescribeTo(::std::ostream* os) const = 0; - - // Describes the negation of this matcher to an ostream. For - // example, if the description of this matcher is "is greater than - // 7", the negated description could be "is not greater than 7". - // You are not required to override this when implementing - // MatcherInterface, but it is highly advised so that your matcher - // can produce good error messages. - virtual void DescribeNegationTo(::std::ostream* os) const { - *os << "not ("; - DescribeTo(os); - *os << ")"; - } -}; - -// The implementation of a matcher. -template -class MatcherInterface : public MatcherDescriberInterface { - public: - // Returns true iff the matcher matches x; also explains the match - // result to 'listener' if necessary (see the next paragraph), in - // the form of a non-restrictive relative clause ("which ...", - // "whose ...", etc) that describes x. For example, the - // MatchAndExplain() method of the Pointee(...) matcher should - // generate an explanation like "which points to ...". - // - // Implementations of MatchAndExplain() should add an explanation of - // the match result *if and only if* they can provide additional - // information that's not already present (or not obvious) in the - // print-out of x and the matcher's description. Whether the match - // succeeds is not a factor in deciding whether an explanation is - // needed, as sometimes the caller needs to print a failure message - // when the match succeeds (e.g. when the matcher is used inside - // Not()). - // - // For example, a "has at least 10 elements" matcher should explain - // what the actual element count is, regardless of the match result, - // as it is useful information to the reader; on the other hand, an - // "is empty" matcher probably only needs to explain what the actual - // size is when the match fails, as it's redundant to say that the - // size is 0 when the value is already known to be empty. - // - // You should override this method when defining a new matcher. - // - // It's the responsibility of the caller (Google Test) to guarantee - // that 'listener' is not NULL. This helps to simplify a matcher's - // implementation when it doesn't care about the performance, as it - // can talk to 'listener' without checking its validity first. - // However, in order to implement dummy listeners efficiently, - // listener->stream() may be NULL. - virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; - - // Inherits these methods from MatcherDescriberInterface: - // virtual void DescribeTo(::std::ostream* os) const = 0; - // virtual void DescribeNegationTo(::std::ostream* os) const; -}; - -namespace internal { - -// Converts a MatcherInterface to a MatcherInterface. -template -class MatcherInterfaceAdapter : public MatcherInterface { - public: - explicit MatcherInterfaceAdapter(const MatcherInterface* impl) - : impl_(impl) {} - ~MatcherInterfaceAdapter() override { delete impl_; } - - void DescribeTo(::std::ostream* os) const override { impl_->DescribeTo(os); } - - void DescribeNegationTo(::std::ostream* os) const override { - impl_->DescribeNegationTo(os); - } - - bool MatchAndExplain(const T& x, - MatchResultListener* listener) const override { - return impl_->MatchAndExplain(x, listener); - } - - private: - const MatcherInterface* const impl_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(MatcherInterfaceAdapter); -}; - -struct AnyEq { - template - bool operator()(const A& a, const B& b) const { return a == b; } -}; -struct AnyNe { - template - bool operator()(const A& a, const B& b) const { return a != b; } -}; -struct AnyLt { - template - bool operator()(const A& a, const B& b) const { return a < b; } -}; -struct AnyGt { - template - bool operator()(const A& a, const B& b) const { return a > b; } -}; -struct AnyLe { - template - bool operator()(const A& a, const B& b) const { return a <= b; } -}; -struct AnyGe { - template - bool operator()(const A& a, const B& b) const { return a >= b; } -}; - -// A match result listener that ignores the explanation. -class DummyMatchResultListener : public MatchResultListener { - public: - DummyMatchResultListener() : MatchResultListener(nullptr) {} - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); -}; - -// A match result listener that forwards the explanation to a given -// ostream. The difference between this and MatchResultListener is -// that the former is concrete. -class StreamMatchResultListener : public MatchResultListener { - public: - explicit StreamMatchResultListener(::std::ostream* os) - : MatchResultListener(os) {} - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); -}; - -// An internal class for implementing Matcher, which will derive -// from it. We put functionalities common to all Matcher -// specializations here to avoid code duplication. -template -class MatcherBase { - public: - // Returns true iff the matcher matches x; also explains the match - // result to 'listener'. - bool MatchAndExplain(const T& x, MatchResultListener* listener) const { - return impl_->MatchAndExplain(x, listener); - } - - // Returns true iff this matcher matches x. - bool Matches(const T& x) const { - DummyMatchResultListener dummy; - return MatchAndExplain(x, &dummy); - } - - // Describes this matcher to an ostream. - void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } - - // Describes the negation of this matcher to an ostream. - void DescribeNegationTo(::std::ostream* os) const { - impl_->DescribeNegationTo(os); - } - - // Explains why x matches, or doesn't match, the matcher. - void ExplainMatchResultTo(const T& x, ::std::ostream* os) const { - StreamMatchResultListener listener(os); - MatchAndExplain(x, &listener); - } - - // Returns the describer for this matcher object; retains ownership - // of the describer, which is only guaranteed to be alive when - // this matcher object is alive. - const MatcherDescriberInterface* GetDescriber() const { - return impl_.get(); - } - - protected: - MatcherBase() {} - - // Constructs a matcher from its implementation. - explicit MatcherBase(const MatcherInterface* impl) : impl_(impl) {} - - template - explicit MatcherBase( - const MatcherInterface* impl, - typename internal::EnableIf< - !internal::IsSame::value>::type* = nullptr) - : impl_(new internal::MatcherInterfaceAdapter(impl)) {} - - MatcherBase(const MatcherBase&) = default; - MatcherBase& operator=(const MatcherBase&) = default; - MatcherBase(MatcherBase&&) = default; - MatcherBase& operator=(MatcherBase&&) = default; - - virtual ~MatcherBase() {} - - private: - std::shared_ptr> impl_; -}; - -} // namespace internal - -// A Matcher is a copyable and IMMUTABLE (except by assignment) -// object that can check whether a value of type T matches. The -// implementation of Matcher is just a std::shared_ptr to const -// MatcherInterface. Don't inherit from Matcher! -template -class Matcher : public internal::MatcherBase { - public: - // Constructs a null matcher. Needed for storing Matcher objects in STL - // containers. A default-constructed matcher is not yet initialized. You - // cannot use it until a valid value has been assigned to it. - explicit Matcher() {} // NOLINT - - // Constructs a matcher from its implementation. - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - - template - explicit Matcher(const MatcherInterface* impl, - typename internal::EnableIf< - !internal::IsSame::value>::type* = nullptr) - : internal::MatcherBase(impl) {} - - // Implicit constructor here allows people to write - // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes - Matcher(T value); // NOLINT -}; - -// The following two specializations allow the user to write str -// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string -// matcher is expected. -template <> -class GTEST_API_ Matcher - : public internal::MatcherBase { - public: - Matcher() {} - - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - - // Allows the user to write str instead of Eq(str) sometimes, where - // str is a std::string object. - Matcher(const std::string& s); // NOLINT - - // Allows the user to write "foo" instead of Eq("foo") sometimes. - Matcher(const char* s); // NOLINT -}; - -template <> -class GTEST_API_ Matcher - : public internal::MatcherBase { - public: - Matcher() {} - - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - - // Allows the user to write str instead of Eq(str) sometimes, where - // str is a string object. - Matcher(const std::string& s); // NOLINT - - // Allows the user to write "foo" instead of Eq("foo") sometimes. - Matcher(const char* s); // NOLINT -}; - -#if GTEST_HAS_ABSL -// The following two specializations allow the user to write str -// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view -// matcher is expected. -template <> -class GTEST_API_ Matcher - : public internal::MatcherBase { - public: - Matcher() {} - - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - - // Allows the user to write str instead of Eq(str) sometimes, where - // str is a std::string object. - Matcher(const std::string& s); // NOLINT - - // Allows the user to write "foo" instead of Eq("foo") sometimes. - Matcher(const char* s); // NOLINT - - // Allows the user to pass absl::string_views directly. - Matcher(absl::string_view s); // NOLINT -}; - -template <> -class GTEST_API_ Matcher - : public internal::MatcherBase { - public: - Matcher() {} - - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - explicit Matcher(const MatcherInterface* impl) - : internal::MatcherBase(impl) {} - - // Allows the user to write str instead of Eq(str) sometimes, where - // str is a std::string object. - Matcher(const std::string& s); // NOLINT - - // Allows the user to write "foo" instead of Eq("foo") sometimes. - Matcher(const char* s); // NOLINT - - // Allows the user to pass absl::string_views directly. - Matcher(absl::string_view s); // NOLINT -}; -#endif // GTEST_HAS_ABSL - -// Prints a matcher in a human-readable format. -template -std::ostream& operator<<(std::ostream& os, const Matcher& matcher) { - matcher.DescribeTo(&os); - return os; -} - -// The PolymorphicMatcher class template makes it easy to implement a -// polymorphic matcher (i.e. a matcher that can match values of more -// than one type, e.g. Eq(n) and NotNull()). -// -// To define a polymorphic matcher, a user should provide an Impl -// class that has a DescribeTo() method and a DescribeNegationTo() -// method, and define a member function (or member function template) -// -// bool MatchAndExplain(const Value& value, -// MatchResultListener* listener) const; -// -// See the definition of NotNull() for a complete example. -template -class PolymorphicMatcher { - public: - explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} - - // Returns a mutable reference to the underlying matcher - // implementation object. - Impl& mutable_impl() { return impl_; } - - // Returns an immutable reference to the underlying matcher - // implementation object. - const Impl& impl() const { return impl_; } - - template - operator Matcher() const { - return Matcher(new MonomorphicImpl(impl_)); - } - - private: - template - class MonomorphicImpl : public MatcherInterface { - public: - explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} - - virtual void DescribeTo(::std::ostream* os) const { impl_.DescribeTo(os); } - - virtual void DescribeNegationTo(::std::ostream* os) const { - impl_.DescribeNegationTo(os); - } - - virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { - return impl_.MatchAndExplain(x, listener); - } - - private: - const Impl impl_; - }; - - Impl impl_; -}; - -// Creates a matcher from its implementation. -// DEPRECATED: Especially in the generic code, prefer: -// Matcher(new MyMatcherImpl(...)); -// -// MakeMatcher may create a Matcher that accepts its argument by value, which -// leads to unnecessary copies & lack of support for non-copyable types. -template -inline Matcher MakeMatcher(const MatcherInterface* impl) { - return Matcher(impl); -} - -// Creates a polymorphic matcher from its implementation. This is -// easier to use than the PolymorphicMatcher constructor as it -// doesn't require you to explicitly write the template argument, e.g. -// -// MakePolymorphicMatcher(foo); -// vs -// PolymorphicMatcher(foo); -template -inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { - return PolymorphicMatcher(impl); -} - -namespace internal { -// Implements a matcher that compares a given value with a -// pre-supplied value using one of the ==, <=, <, etc, operators. The -// two values being compared don't have to have the same type. -// -// The matcher defined here is polymorphic (for example, Eq(5) can be -// used to match an int, a short, a double, etc). Therefore we use -// a template type conversion operator in the implementation. -// -// The following template definition assumes that the Rhs parameter is -// a "bare" type (i.e. neither 'const T' nor 'T&'). -template -class ComparisonBase { - public: - explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {} - template - operator Matcher() const { - return Matcher(new Impl(rhs_)); - } - - private: - template - static const T& Unwrap(const T& v) { return v; } - template - static const T& Unwrap(std::reference_wrapper v) { return v; } - - template - class Impl : public MatcherInterface { - public: - explicit Impl(const Rhs& rhs) : rhs_(rhs) {} - bool MatchAndExplain(Lhs lhs, - MatchResultListener* /* listener */) const override { - return Op()(lhs, Unwrap(rhs_)); - } - void DescribeTo(::std::ostream* os) const override { - *os << D::Desc() << " "; - UniversalPrint(Unwrap(rhs_), os); - } - void DescribeNegationTo(::std::ostream* os) const override { - *os << D::NegatedDesc() << " "; - UniversalPrint(Unwrap(rhs_), os); - } - - private: - Rhs rhs_; - }; - Rhs rhs_; -}; - -template -class EqMatcher : public ComparisonBase, Rhs, AnyEq> { - public: - explicit EqMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyEq>(rhs) { } - static const char* Desc() { return "is equal to"; } - static const char* NegatedDesc() { return "isn't equal to"; } -}; -template -class NeMatcher : public ComparisonBase, Rhs, AnyNe> { - public: - explicit NeMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyNe>(rhs) { } - static const char* Desc() { return "isn't equal to"; } - static const char* NegatedDesc() { return "is equal to"; } -}; -template -class LtMatcher : public ComparisonBase, Rhs, AnyLt> { - public: - explicit LtMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyLt>(rhs) { } - static const char* Desc() { return "is <"; } - static const char* NegatedDesc() { return "isn't <"; } -}; -template -class GtMatcher : public ComparisonBase, Rhs, AnyGt> { - public: - explicit GtMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyGt>(rhs) { } - static const char* Desc() { return "is >"; } - static const char* NegatedDesc() { return "isn't >"; } -}; -template -class LeMatcher : public ComparisonBase, Rhs, AnyLe> { - public: - explicit LeMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyLe>(rhs) { } - static const char* Desc() { return "is <="; } - static const char* NegatedDesc() { return "isn't <="; } -}; -template -class GeMatcher : public ComparisonBase, Rhs, AnyGe> { - public: - explicit GeMatcher(const Rhs& rhs) - : ComparisonBase, Rhs, AnyGe>(rhs) { } - static const char* Desc() { return "is >="; } - static const char* NegatedDesc() { return "isn't >="; } -}; - -// Implements polymorphic matchers MatchesRegex(regex) and -// ContainsRegex(regex), which can be used as a Matcher as long as -// T can be converted to a string. -class MatchesRegexMatcher { - public: - MatchesRegexMatcher(const RE* regex, bool full_match) - : regex_(regex), full_match_(full_match) {} - -#if GTEST_HAS_ABSL - bool MatchAndExplain(const absl::string_view& s, - MatchResultListener* listener) const { - return MatchAndExplain(std::string(s), listener); - } -#endif // GTEST_HAS_ABSL - - // Accepts pointer types, particularly: - // const char* - // char* - // const wchar_t* - // wchar_t* - template - bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { - return s != nullptr && MatchAndExplain(std::string(s), listener); - } - - // Matches anything that can convert to std::string. - // - // This is a template, not just a plain function with const std::string&, - // because absl::string_view has some interfering non-explicit constructors. - template - bool MatchAndExplain(const MatcheeStringType& s, - MatchResultListener* /* listener */) const { - const std::string& s2(s); - return full_match_ ? RE::FullMatch(s2, *regex_) - : RE::PartialMatch(s2, *regex_); - } - - void DescribeTo(::std::ostream* os) const { - *os << (full_match_ ? "matches" : "contains") << " regular expression "; - UniversalPrinter::Print(regex_->pattern(), os); - } - - void DescribeNegationTo(::std::ostream* os) const { - *os << "doesn't " << (full_match_ ? "match" : "contain") - << " regular expression "; - UniversalPrinter::Print(regex_->pattern(), os); - } - - private: - const std::shared_ptr regex_; - const bool full_match_; -}; -} // namespace internal - -// Matches a string that fully matches regular expression 'regex'. -// The matcher takes ownership of 'regex'. -inline PolymorphicMatcher MatchesRegex( - const internal::RE* regex) { - return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true)); -} -inline PolymorphicMatcher MatchesRegex( - const std::string& regex) { - return MatchesRegex(new internal::RE(regex)); -} - -// Matches a string that contains regular expression 'regex'. -// The matcher takes ownership of 'regex'. -inline PolymorphicMatcher ContainsRegex( - const internal::RE* regex) { - return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false)); -} -inline PolymorphicMatcher ContainsRegex( - const std::string& regex) { - return ContainsRegex(new internal::RE(regex)); -} - -// Creates a polymorphic matcher that matches anything equal to x. -// Note: if the parameter of Eq() were declared as const T&, Eq("foo") -// wouldn't compile. -template -inline internal::EqMatcher Eq(T x) { return internal::EqMatcher(x); } - -// Constructs a Matcher from a 'value' of type T. The constructed -// matcher matches any value that's equal to 'value'. -template -Matcher::Matcher(T value) { *this = Eq(value); } - -// Creates a monomorphic matcher that matches anything with type Lhs -// and equal to rhs. A user may need to use this instead of Eq(...) -// in order to resolve an overloading ambiguity. -// -// TypedEq(x) is just a convenient short-hand for Matcher(Eq(x)) -// or Matcher(x), but more readable than the latter. -// -// We could define similar monomorphic matchers for other comparison -// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do -// it yet as those are used much less than Eq() in practice. A user -// can always write Matcher(Lt(5)) to be explicit about the type, -// for example. -template -inline Matcher TypedEq(const Rhs& rhs) { return Eq(rhs); } - -// Creates a polymorphic matcher that matches anything >= x. -template -inline internal::GeMatcher Ge(Rhs x) { - return internal::GeMatcher(x); -} - -// Creates a polymorphic matcher that matches anything > x. -template -inline internal::GtMatcher Gt(Rhs x) { - return internal::GtMatcher(x); -} - -// Creates a polymorphic matcher that matches anything <= x. -template -inline internal::LeMatcher Le(Rhs x) { - return internal::LeMatcher(x); -} - -// Creates a polymorphic matcher that matches anything < x. -template -inline internal::LtMatcher Lt(Rhs x) { - return internal::LtMatcher(x); -} - -// Creates a polymorphic matcher that matches anything != x. -template -inline internal::NeMatcher Ne(Rhs x) { - return internal::NeMatcher(x); -} -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046 - -#endif // GTEST_INCLUDE_GTEST_GTEST_MATCHERS_H_ - -#include -#include - -namespace testing { -namespace internal { - -GTEST_DECLARE_string_(internal_run_death_test); - -// Names of the flags (needed for parsing Google Test flags). -const char kDeathTestStyleFlag[] = "death_test_style"; -const char kDeathTestUseFork[] = "death_test_use_fork"; -const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; - -#if GTEST_HAS_DEATH_TEST - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -// DeathTest is a class that hides much of the complexity of the -// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method -// returns a concrete class that depends on the prevailing death test -// style, as defined by the --gtest_death_test_style and/or -// --gtest_internal_run_death_test flags. - -// In describing the results of death tests, these terms are used with -// the corresponding definitions: -// -// exit status: The integer exit information in the format specified -// by wait(2) -// exit code: The integer code passed to exit(3), _exit(2), or -// returned from main() -class GTEST_API_ DeathTest { - public: - // Create returns false if there was an error determining the - // appropriate action to take for the current death test; for example, - // if the gtest_death_test_style flag is set to an invalid value. - // The LastMessage method will return a more detailed message in that - // case. Otherwise, the DeathTest pointer pointed to by the "test" - // argument is set. If the death test should be skipped, the pointer - // is set to NULL; otherwise, it is set to the address of a new concrete - // DeathTest object that controls the execution of the current test. - static bool Create(const char* statement, Matcher matcher, - const char* file, int line, DeathTest** test); - DeathTest(); - virtual ~DeathTest() { } - - // A helper class that aborts a death test when it's deleted. - class ReturnSentinel { - public: - explicit ReturnSentinel(DeathTest* test) : test_(test) { } - ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } - private: - DeathTest* const test_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); - } GTEST_ATTRIBUTE_UNUSED_; - - // An enumeration of possible roles that may be taken when a death - // test is encountered. EXECUTE means that the death test logic should - // be executed immediately. OVERSEE means that the program should prepare - // the appropriate environment for a child process to execute the death - // test, then wait for it to complete. - enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; - - // An enumeration of the three reasons that a test might be aborted. - enum AbortReason { - TEST_ENCOUNTERED_RETURN_STATEMENT, - TEST_THREW_EXCEPTION, - TEST_DID_NOT_DIE - }; - - // Assumes one of the above roles. - virtual TestRole AssumeRole() = 0; - - // Waits for the death test to finish and returns its status. - virtual int Wait() = 0; - - // Returns true if the death test passed; that is, the test process - // exited during the test, its exit status matches a user-supplied - // predicate, and its stderr output matches a user-supplied regular - // expression. - // The user-supplied predicate may be a macro expression rather - // than a function pointer or functor, or else Wait and Passed could - // be combined. - virtual bool Passed(bool exit_status_ok) = 0; - - // Signals that the death test did not die as expected. - virtual void Abort(AbortReason reason) = 0; - - // Returns a human-readable outcome message regarding the outcome of - // the last death test. - static const char* LastMessage(); - - static void set_last_death_test_message(const std::string& message); - - private: - // A string containing a description of the outcome of the last death test. - static std::string last_death_test_message_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); -}; - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -// Factory interface for death tests. May be mocked out for testing. -class DeathTestFactory { - public: - virtual ~DeathTestFactory() { } - virtual bool Create(const char* statement, - Matcher matcher, const char* file, - int line, DeathTest** test) = 0; -}; - -// A concrete DeathTestFactory implementation for normal use. -class DefaultDeathTestFactory : public DeathTestFactory { - public: - bool Create(const char* statement, Matcher matcher, - const char* file, int line, DeathTest** test) override; -}; - -// Returns true if exit_status describes a process that was terminated -// by a signal, or exited normally with a nonzero exit code. -GTEST_API_ bool ExitedUnsuccessfully(int exit_status); - -// A string passed to EXPECT_DEATH (etc.) is caught by one of these overloads -// and interpreted as a regex (rather than an Eq matcher) for legacy -// compatibility. -inline Matcher MakeDeathTestMatcher( - ::testing::internal::RE regex) { - return ContainsRegex(regex.pattern()); -} -inline Matcher MakeDeathTestMatcher(const char* regex) { - return ContainsRegex(regex); -} -inline Matcher MakeDeathTestMatcher( - const ::std::string& regex) { - return ContainsRegex(regex); -} - -// If a Matcher is passed to EXPECT_DEATH (etc.), it's -// used directly. -inline Matcher MakeDeathTestMatcher( - Matcher matcher) { - return matcher; -} - -// Traps C++ exceptions escaping statement and reports them as test -// failures. Note that trapping SEH exceptions is not implemented here. -# if GTEST_HAS_EXCEPTIONS -# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ - try { \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - } catch (const ::std::exception& gtest_exception) { \ - fprintf(\ - stderr, \ - "\n%s: Caught std::exception-derived exception escaping the " \ - "death test statement. Exception message: %s\n", \ - ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ - gtest_exception.what()); \ - fflush(stderr); \ - death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ - } catch (...) { \ - death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ - } - -# else -# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) - -# endif - -// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, -// ASSERT_EXIT*, and EXPECT_EXIT*. -#define GTEST_DEATH_TEST_(statement, predicate, regex_or_matcher, fail) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - ::testing::internal::DeathTest* gtest_dt; \ - if (!::testing::internal::DeathTest::Create( \ - #statement, \ - ::testing::internal::MakeDeathTestMatcher(regex_or_matcher), \ - __FILE__, __LINE__, >est_dt)) { \ - goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ - } \ - if (gtest_dt != nullptr) { \ - std::unique_ptr< ::testing::internal::DeathTest> gtest_dt_ptr(gtest_dt); \ - switch (gtest_dt->AssumeRole()) { \ - case ::testing::internal::DeathTest::OVERSEE_TEST: \ - if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ - goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ - } \ - break; \ - case ::testing::internal::DeathTest::EXECUTE_TEST: { \ - ::testing::internal::DeathTest::ReturnSentinel gtest_sentinel( \ - gtest_dt); \ - GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ - gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ - break; \ - } \ - default: \ - break; \ - } \ - } \ - } else \ - GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__) \ - : fail(::testing::internal::DeathTest::LastMessage()) -// The symbol "fail" here expands to something into which a message -// can be streamed. - -// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in -// NDEBUG mode. In this case we need the statements to be executed and the macro -// must accept a streamed message even though the message is never printed. -// The regex object is not evaluated, but it is used to prevent "unused" -// warnings and to avoid an expression that doesn't compile in debug mode. -#define GTEST_EXECUTE_STATEMENT_(statement, regex_or_matcher) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - } else if (!::testing::internal::AlwaysTrue()) { \ - ::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \ - } else \ - ::testing::Message() - -// A class representing the parsed contents of the -// --gtest_internal_run_death_test flag, as it existed when -// RUN_ALL_TESTS was called. -class InternalRunDeathTestFlag { - public: - InternalRunDeathTestFlag(const std::string& a_file, - int a_line, - int an_index, - int a_write_fd) - : file_(a_file), line_(a_line), index_(an_index), - write_fd_(a_write_fd) {} - - ~InternalRunDeathTestFlag() { - if (write_fd_ >= 0) - posix::Close(write_fd_); - } - - const std::string& file() const { return file_; } - int line() const { return line_; } - int index() const { return index_; } - int write_fd() const { return write_fd_; } - - private: - std::string file_; - int line_; - int index_; - int write_fd_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); -}; - -// Returns a newly created InternalRunDeathTestFlag object with fields -// initialized from the GTEST_FLAG(internal_run_death_test) flag if -// the flag is specified; otherwise returns NULL. -InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); - -#endif // GTEST_HAS_DEATH_TEST - -} // namespace internal -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ - -namespace testing { - -// This flag controls the style of death tests. Valid values are "threadsafe", -// meaning that the death test child process will re-execute the test binary -// from the start, running only a single death test, or "fast", -// meaning that the child process will execute the test logic immediately -// after forking. -GTEST_DECLARE_string_(death_test_style); - -#if GTEST_HAS_DEATH_TEST - -namespace internal { - -// Returns a Boolean value indicating whether the caller is currently -// executing in the context of the death test child process. Tools such as -// Valgrind heap checkers may need this to modify their behavior in death -// tests. IMPORTANT: This is an internal utility. Using it may break the -// implementation of death tests. User code MUST NOT use it. -GTEST_API_ bool InDeathTestChild(); - -} // namespace internal - -// The following macros are useful for writing death tests. - -// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is -// executed: -// -// 1. It generates a warning if there is more than one active -// thread. This is because it's safe to fork() or clone() only -// when there is a single thread. -// -// 2. The parent process clone()s a sub-process and runs the death -// test in it; the sub-process exits with code 0 at the end of the -// death test, if it hasn't exited already. -// -// 3. The parent process waits for the sub-process to terminate. -// -// 4. The parent process checks the exit code and error message of -// the sub-process. -// -// Examples: -// -// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); -// for (int i = 0; i < 5; i++) { -// EXPECT_DEATH(server.ProcessRequest(i), -// "Invalid request .* in ProcessRequest()") -// << "Failed to die on request " << i; -// } -// -// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); -// -// bool KilledBySIGHUP(int exit_code) { -// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; -// } -// -// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); -// -// On the regular expressions used in death tests: -// -// GOOGLETEST_CM0005 DO NOT DELETE -// On POSIX-compliant systems (*nix), we use the library, -// which uses the POSIX extended regex syntax. -// -// On other platforms (e.g. Windows or Mac), we only support a simple regex -// syntax implemented as part of Google Test. This limited -// implementation should be enough most of the time when writing -// death tests; though it lacks many features you can find in PCRE -// or POSIX extended regex syntax. For example, we don't support -// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and -// repetition count ("x{5,7}"), among others. -// -// Below is the syntax that we do support. We chose it to be a -// subset of both PCRE and POSIX extended regex, so it's easy to -// learn wherever you come from. In the following: 'A' denotes a -// literal character, period (.), or a single \\ escape sequence; -// 'x' and 'y' denote regular expressions; 'm' and 'n' are for -// natural numbers. -// -// c matches any literal character c -// \\d matches any decimal digit -// \\D matches any character that's not a decimal digit -// \\f matches \f -// \\n matches \n -// \\r matches \r -// \\s matches any ASCII whitespace, including \n -// \\S matches any character that's not a whitespace -// \\t matches \t -// \\v matches \v -// \\w matches any letter, _, or decimal digit -// \\W matches any character that \\w doesn't match -// \\c matches any literal character c, which must be a punctuation -// . matches any single character except \n -// A? matches 0 or 1 occurrences of A -// A* matches 0 or many occurrences of A -// A+ matches 1 or many occurrences of A -// ^ matches the beginning of a string (not that of each line) -// $ matches the end of a string (not that of each line) -// xy matches x followed by y -// -// If you accidentally use PCRE or POSIX extended regex features -// not implemented by us, you will get a run-time failure. In that -// case, please try to rewrite your regular expression within the -// above syntax. -// -// This implementation is *not* meant to be as highly tuned or robust -// as a compiled regex library, but should perform well enough for a -// death test, which already incurs significant overhead by launching -// a child process. -// -// Known caveats: -// -// A "threadsafe" style death test obtains the path to the test -// program from argv[0] and re-executes it in the sub-process. For -// simplicity, the current implementation doesn't search the PATH -// when launching the sub-process. This means that the user must -// invoke the test program via a path that contains at least one -// path separator (e.g. path/to/foo_test and -// /absolute/path/to/bar_test are fine, but foo_test is not). This -// is rarely a problem as people usually don't put the test binary -// directory in PATH. -// - -// Asserts that a given statement causes the program to exit, with an -// integer exit status that satisfies predicate, and emitting error output -// that matches regex. -# define ASSERT_EXIT(statement, predicate, regex) \ - GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) - -// Like ASSERT_EXIT, but continues on to successive tests in the -// test suite, if any: -# define EXPECT_EXIT(statement, predicate, regex) \ - GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) - -// Asserts that a given statement causes the program to exit, either by -// explicitly exiting with a nonzero exit code or being killed by a -// signal, and emitting error output that matches regex. -# define ASSERT_DEATH(statement, regex) \ - ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) - -// Like ASSERT_DEATH, but continues on to successive tests in the -// test suite, if any: -# define EXPECT_DEATH(statement, regex) \ - EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) - -// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: - -// Tests that an exit code describes a normal exit with a given exit code. -class GTEST_API_ ExitedWithCode { - public: - explicit ExitedWithCode(int exit_code); - bool operator()(int exit_status) const; - private: - // No implementation - assignment is unsupported. - void operator=(const ExitedWithCode& other); - - const int exit_code_; -}; - -# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA -// Tests that an exit code describes an exit due to termination by a -// given signal. -// GOOGLETEST_CM0006 DO NOT DELETE -class GTEST_API_ KilledBySignal { - public: - explicit KilledBySignal(int signum); - bool operator()(int exit_status) const; - private: - const int signum_; -}; -# endif // !GTEST_OS_WINDOWS - -// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. -// The death testing framework causes this to have interesting semantics, -// since the sideeffects of the call are only visible in opt mode, and not -// in debug mode. -// -// In practice, this can be used to test functions that utilize the -// LOG(DFATAL) macro using the following style: -// -// int DieInDebugOr12(int* sideeffect) { -// if (sideeffect) { -// *sideeffect = 12; -// } -// LOG(DFATAL) << "death"; -// return 12; -// } -// -// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) { -// int sideeffect = 0; -// // Only asserts in dbg. -// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); -// -// #ifdef NDEBUG -// // opt-mode has sideeffect visible. -// EXPECT_EQ(12, sideeffect); -// #else -// // dbg-mode no visible sideeffect. -// EXPECT_EQ(0, sideeffect); -// #endif -// } -// -// This will assert that DieInDebugReturn12InOpt() crashes in debug -// mode, usually due to a DCHECK or LOG(DFATAL), but returns the -// appropriate fallback value (12 in this case) in opt mode. If you -// need to test that a function has appropriate side-effects in opt -// mode, include assertions against the side-effects. A general -// pattern for this is: -// -// EXPECT_DEBUG_DEATH({ -// // Side-effects here will have an effect after this statement in -// // opt mode, but none in debug mode. -// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); -// }, "death"); -// -# ifdef NDEBUG - -# define EXPECT_DEBUG_DEATH(statement, regex) \ - GTEST_EXECUTE_STATEMENT_(statement, regex) - -# define ASSERT_DEBUG_DEATH(statement, regex) \ - GTEST_EXECUTE_STATEMENT_(statement, regex) - -# else - -# define EXPECT_DEBUG_DEATH(statement, regex) \ - EXPECT_DEATH(statement, regex) - -# define ASSERT_DEBUG_DEATH(statement, regex) \ - ASSERT_DEATH(statement, regex) - -# endif // NDEBUG for EXPECT_DEBUG_DEATH -#endif // GTEST_HAS_DEATH_TEST - -// This macro is used for implementing macros such as -// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where -// death tests are not supported. Those macros must compile on such systems -// iff EXPECT_DEATH and ASSERT_DEATH compile with the same parameters on -// systems that support death tests. This allows one to write such a macro -// on a system that does not support death tests and be sure that it will -// compile on a death-test supporting system. It is exposed publicly so that -// systems that have death-tests with stricter requirements than -// GTEST_HAS_DEATH_TEST can write their own equivalent of -// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED. -// -// Parameters: -// statement - A statement that a macro such as EXPECT_DEATH would test -// for program termination. This macro has to make sure this -// statement is compiled but not executed, to ensure that -// EXPECT_DEATH_IF_SUPPORTED compiles with a certain -// parameter iff EXPECT_DEATH compiles with it. -// regex - A regex that a macro such as EXPECT_DEATH would use to test -// the output of statement. This parameter has to be -// compiled but not evaluated by this macro, to ensure that -// this macro only accepts expressions that a macro such as -// EXPECT_DEATH would accept. -// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED -// and a return statement for ASSERT_DEATH_IF_SUPPORTED. -// This ensures that ASSERT_DEATH_IF_SUPPORTED will not -// compile inside functions where ASSERT_DEATH doesn't -// compile. -// -// The branch that has an always false condition is used to ensure that -// statement and regex are compiled (and thus syntactically correct) but -// never executed. The unreachable code macro protects the terminator -// statement from generating an 'unreachable code' warning in case -// statement unconditionally returns or throws. The Message constructor at -// the end allows the syntax of streaming additional messages into the -// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. -# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (::testing::internal::AlwaysTrue()) { \ - GTEST_LOG_(WARNING) \ - << "Death tests are not supported on this platform.\n" \ - << "Statement '" #statement "' cannot be verified."; \ - } else if (::testing::internal::AlwaysFalse()) { \ - ::testing::internal::RE::PartialMatch(".*", (regex)); \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ - terminator; \ - } else \ - ::testing::Message() - -// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and -// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if -// death tests are supported; otherwise they just issue a warning. This is -// useful when you are combining death test assertions with normal test -// assertions in one test. -#if GTEST_HAS_DEATH_TEST -# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEATH(statement, regex) -# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ - ASSERT_DEATH(statement, regex) -#else -# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, ) -# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ - GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return) -#endif - -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Macros and functions for implementing parameterized tests -// in Google C++ Testing and Mocking Framework (Google Test) -// -// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! -// -// GOOGLETEST_CM0001 DO NOT DELETE -#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ -#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ - - -// Value-parameterized tests allow you to test your code with different -// parameters without writing multiple copies of the same test. -// -// Here is how you use value-parameterized tests: - -#if 0 - -// To write value-parameterized tests, first you should define a fixture -// class. It is usually derived from testing::TestWithParam (see below for -// another inheritance scheme that's sometimes useful in more complicated -// class hierarchies), where the type of your parameter values. -// TestWithParam is itself derived from testing::Test. T can be any -// copyable type. If it's a raw pointer, you are responsible for managing the -// lifespan of the pointed values. - -class FooTest : public ::testing::TestWithParam { - // You can implement all the usual class fixture members here. -}; - -// Then, use the TEST_P macro to define as many parameterized tests -// for this fixture as you want. The _P suffix is for "parameterized" -// or "pattern", whichever you prefer to think. - -TEST_P(FooTest, DoesBlah) { - // Inside a test, access the test parameter with the GetParam() method - // of the TestWithParam class: - EXPECT_TRUE(foo.Blah(GetParam())); - ... -} - -TEST_P(FooTest, HasBlahBlah) { - ... -} - -// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test -// case with any set of parameters you want. Google Test defines a number -// of functions for generating test parameters. They return what we call -// (surprise!) parameter generators. Here is a summary of them, which -// are all in the testing namespace: -// -// -// Range(begin, end [, step]) - Yields values {begin, begin+step, -// begin+step+step, ...}. The values do not -// include end. step defaults to 1. -// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. -// ValuesIn(container) - Yields values from a C-style array, an STL -// ValuesIn(begin,end) container, or an iterator range [begin, end). -// Bool() - Yields sequence {false, true}. -// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product -// for the math savvy) of the values generated -// by the N generators. -// -// For more details, see comments at the definitions of these functions below -// in this file. -// -// The following statement will instantiate tests from the FooTest test suite -// each with parameter values "meeny", "miny", and "moe". - -INSTANTIATE_TEST_SUITE_P(InstantiationName, - FooTest, - Values("meeny", "miny", "moe")); - -// To distinguish different instances of the pattern, (yes, you -// can instantiate it more than once) the first argument to the -// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the -// actual test suite name. Remember to pick unique prefixes for different -// instantiations. The tests from the instantiation above will have -// these names: -// -// * InstantiationName/FooTest.DoesBlah/0 for "meeny" -// * InstantiationName/FooTest.DoesBlah/1 for "miny" -// * InstantiationName/FooTest.DoesBlah/2 for "moe" -// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" -// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" -// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" -// -// You can use these names in --gtest_filter. -// -// This statement will instantiate all tests from FooTest again, each -// with parameter values "cat" and "dog": - -const char* pets[] = {"cat", "dog"}; -INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); - -// The tests from the instantiation above will have these names: -// -// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" -// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" -// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" -// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" -// -// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests -// in the given test suite, whether their definitions come before or -// AFTER the INSTANTIATE_TEST_SUITE_P statement. -// -// Please also note that generator expressions (including parameters to the -// generators) are evaluated in InitGoogleTest(), after main() has started. -// This allows the user on one hand, to adjust generator parameters in order -// to dynamically determine a set of tests to run and on the other hand, -// give the user a chance to inspect the generated tests with Google Test -// reflection API before RUN_ALL_TESTS() is executed. -// -// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc -// for more examples. -// -// In the future, we plan to publish the API for defining new parameter -// generators. But for now this interface remains part of the internal -// implementation and is subject to change. -// -// -// A parameterized test fixture must be derived from testing::Test and from -// testing::WithParamInterface, where T is the type of the parameter -// values. Inheriting from TestWithParam satisfies that requirement because -// TestWithParam inherits from both Test and WithParamInterface. In more -// complicated hierarchies, however, it is occasionally useful to inherit -// separately from Test and WithParamInterface. For example: - -class BaseTest : public ::testing::Test { - // You can inherit all the usual members for a non-parameterized test - // fixture here. -}; - -class DerivedTest : public BaseTest, public ::testing::WithParamInterface { - // The usual test fixture members go here too. -}; - -TEST_F(BaseTest, HasFoo) { - // This is an ordinary non-parameterized test. -} - -TEST_P(DerivedTest, DoesBlah) { - // GetParam works just the same here as if you inherit from TestWithParam. - EXPECT_TRUE(foo.Blah(GetParam())); -} - -#endif // 0 - -#include - -// Copyright 2008 Google Inc. -// All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -// Type and function utilities for implementing parameterized tests. - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ -#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ - -#include - -#include -#include -#include -#include -#include -#include -#include - - -namespace testing { -// Input to a parameterized test name generator, describing a test parameter. -// Consists of the parameter value and the integer parameter index. -template -struct TestParamInfo { - TestParamInfo(const ParamType& a_param, size_t an_index) : - param(a_param), - index(an_index) {} - ParamType param; - size_t index; -}; - -// A builtin parameterized test name generator which returns the result of -// testing::PrintToString. -struct PrintToStringParamName { - template - std::string operator()(const TestParamInfo& info) const { - return PrintToString(info.param); - } -}; - -namespace internal { - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// Utility Functions - -// Outputs a message explaining invalid registration of different -// fixture class for the same test suite. This may happen when -// TEST_P macro is used to define two tests with the same name -// but in different namespaces. -GTEST_API_ void ReportInvalidTestSuiteType(const char* test_suite_name, - CodeLocation code_location); - -template class ParamGeneratorInterface; -template class ParamGenerator; - -// Interface for iterating over elements provided by an implementation -// of ParamGeneratorInterface. -template -class ParamIteratorInterface { - public: - virtual ~ParamIteratorInterface() {} - // A pointer to the base generator instance. - // Used only for the purposes of iterator comparison - // to make sure that two iterators belong to the same generator. - virtual const ParamGeneratorInterface* BaseGenerator() const = 0; - // Advances iterator to point to the next element - // provided by the generator. The caller is responsible - // for not calling Advance() on an iterator equal to - // BaseGenerator()->End(). - virtual void Advance() = 0; - // Clones the iterator object. Used for implementing copy semantics - // of ParamIterator. - virtual ParamIteratorInterface* Clone() const = 0; - // Dereferences the current iterator and provides (read-only) access - // to the pointed value. It is the caller's responsibility not to call - // Current() on an iterator equal to BaseGenerator()->End(). - // Used for implementing ParamGenerator::operator*(). - virtual const T* Current() const = 0; - // Determines whether the given iterator and other point to the same - // element in the sequence generated by the generator. - // Used for implementing ParamGenerator::operator==(). - virtual bool Equals(const ParamIteratorInterface& other) const = 0; -}; - -// Class iterating over elements provided by an implementation of -// ParamGeneratorInterface. It wraps ParamIteratorInterface -// and implements the const forward iterator concept. -template -class ParamIterator { - public: - typedef T value_type; - typedef const T& reference; - typedef ptrdiff_t difference_type; - - // ParamIterator assumes ownership of the impl_ pointer. - ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} - ParamIterator& operator=(const ParamIterator& other) { - if (this != &other) - impl_.reset(other.impl_->Clone()); - return *this; - } - - const T& operator*() const { return *impl_->Current(); } - const T* operator->() const { return impl_->Current(); } - // Prefix version of operator++. - ParamIterator& operator++() { - impl_->Advance(); - return *this; - } - // Postfix version of operator++. - ParamIterator operator++(int /*unused*/) { - ParamIteratorInterface* clone = impl_->Clone(); - impl_->Advance(); - return ParamIterator(clone); - } - bool operator==(const ParamIterator& other) const { - return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); - } - bool operator!=(const ParamIterator& other) const { - return !(*this == other); - } - - private: - friend class ParamGenerator; - explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} - std::unique_ptr > impl_; -}; - -// ParamGeneratorInterface is the binary interface to access generators -// defined in other translation units. -template -class ParamGeneratorInterface { - public: - typedef T ParamType; - - virtual ~ParamGeneratorInterface() {} - - // Generator interface definition - virtual ParamIteratorInterface* Begin() const = 0; - virtual ParamIteratorInterface* End() const = 0; -}; - -// Wraps ParamGeneratorInterface and provides general generator syntax -// compatible with the STL Container concept. -// This class implements copy initialization semantics and the contained -// ParamGeneratorInterface instance is shared among all copies -// of the original object. This is possible because that instance is immutable. -template -class ParamGenerator { - public: - typedef ParamIterator iterator; - - explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} - ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} - - ParamGenerator& operator=(const ParamGenerator& other) { - impl_ = other.impl_; - return *this; - } - - iterator begin() const { return iterator(impl_->Begin()); } - iterator end() const { return iterator(impl_->End()); } - - private: - std::shared_ptr > impl_; -}; - -// Generates values from a range of two comparable values. Can be used to -// generate sequences of user-defined types that implement operator+() and -// operator<(). -// This class is used in the Range() function. -template -class RangeGenerator : public ParamGeneratorInterface { - public: - RangeGenerator(T begin, T end, IncrementT step) - : begin_(begin), end_(end), - step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} - ~RangeGenerator() override {} - - ParamIteratorInterface* Begin() const override { - return new Iterator(this, begin_, 0, step_); - } - ParamIteratorInterface* End() const override { - return new Iterator(this, end_, end_index_, step_); - } - - private: - class Iterator : public ParamIteratorInterface { - public: - Iterator(const ParamGeneratorInterface* base, T value, int index, - IncrementT step) - : base_(base), value_(value), index_(index), step_(step) {} - ~Iterator() override {} - - const ParamGeneratorInterface* BaseGenerator() const override { - return base_; - } - void Advance() override { - value_ = static_cast(value_ + step_); - index_++; - } - ParamIteratorInterface* Clone() const override { - return new Iterator(*this); - } - const T* Current() const override { return &value_; } - bool Equals(const ParamIteratorInterface& other) const override { - // Having the same base generator guarantees that the other - // iterator is of the same type and we can downcast. - GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) - << "The program attempted to compare iterators " - << "from different generators." << std::endl; - const int other_index = - CheckedDowncastToActualType(&other)->index_; - return index_ == other_index; - } - - private: - Iterator(const Iterator& other) - : ParamIteratorInterface(), - base_(other.base_), value_(other.value_), index_(other.index_), - step_(other.step_) {} - - // No implementation - assignment is unsupported. - void operator=(const Iterator& other); - - const ParamGeneratorInterface* const base_; - T value_; - int index_; - const IncrementT step_; - }; // class RangeGenerator::Iterator - - static int CalculateEndIndex(const T& begin, - const T& end, - const IncrementT& step) { - int end_index = 0; - for (T i = begin; i < end; i = static_cast(i + step)) - end_index++; - return end_index; - } - - // No implementation - assignment is unsupported. - void operator=(const RangeGenerator& other); - - const T begin_; - const T end_; - const IncrementT step_; - // The index for the end() iterator. All the elements in the generated - // sequence are indexed (0-based) to aid iterator comparison. - const int end_index_; -}; // class RangeGenerator - - -// Generates values from a pair of STL-style iterators. Used in the -// ValuesIn() function. The elements are copied from the source range -// since the source can be located on the stack, and the generator -// is likely to persist beyond that stack frame. -template -class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { - public: - template - ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) - : container_(begin, end) {} - ~ValuesInIteratorRangeGenerator() override {} - - ParamIteratorInterface* Begin() const override { - return new Iterator(this, container_.begin()); - } - ParamIteratorInterface* End() const override { - return new Iterator(this, container_.end()); - } - - private: - typedef typename ::std::vector ContainerType; - - class Iterator : public ParamIteratorInterface { - public: - Iterator(const ParamGeneratorInterface* base, - typename ContainerType::const_iterator iterator) - : base_(base), iterator_(iterator) {} - ~Iterator() override {} - - const ParamGeneratorInterface* BaseGenerator() const override { - return base_; - } - void Advance() override { - ++iterator_; - value_.reset(); - } - ParamIteratorInterface* Clone() const override { - return new Iterator(*this); - } - // We need to use cached value referenced by iterator_ because *iterator_ - // can return a temporary object (and of type other then T), so just - // having "return &*iterator_;" doesn't work. - // value_ is updated here and not in Advance() because Advance() - // can advance iterator_ beyond the end of the range, and we cannot - // detect that fact. The client code, on the other hand, is - // responsible for not calling Current() on an out-of-range iterator. - const T* Current() const override { - if (value_.get() == nullptr) value_.reset(new T(*iterator_)); - return value_.get(); - } - bool Equals(const ParamIteratorInterface& other) const override { - // Having the same base generator guarantees that the other - // iterator is of the same type and we can downcast. - GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) - << "The program attempted to compare iterators " - << "from different generators." << std::endl; - return iterator_ == - CheckedDowncastToActualType(&other)->iterator_; - } - - private: - Iterator(const Iterator& other) - // The explicit constructor call suppresses a false warning - // emitted by gcc when supplied with the -Wextra option. - : ParamIteratorInterface(), - base_(other.base_), - iterator_(other.iterator_) {} - - const ParamGeneratorInterface* const base_; - typename ContainerType::const_iterator iterator_; - // A cached value of *iterator_. We keep it here to allow access by - // pointer in the wrapping iterator's operator->(). - // value_ needs to be mutable to be accessed in Current(). - // Use of std::unique_ptr helps manage cached value's lifetime, - // which is bound by the lifespan of the iterator itself. - mutable std::unique_ptr value_; - }; // class ValuesInIteratorRangeGenerator::Iterator - - // No implementation - assignment is unsupported. - void operator=(const ValuesInIteratorRangeGenerator& other); - - const ContainerType container_; -}; // class ValuesInIteratorRangeGenerator - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Default parameterized test name generator, returns a string containing the -// integer test parameter index. -template -std::string DefaultParamName(const TestParamInfo& info) { - Message name_stream; - name_stream << info.index; - return name_stream.GetString(); -} - -template -void TestNotEmpty() { - static_assert(sizeof(T) == 0, "Empty arguments are not allowed."); -} -template -void TestNotEmpty(const T&) {} - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Stores a parameter value and later creates tests parameterized with that -// value. -template -class ParameterizedTestFactory : public TestFactoryBase { - public: - typedef typename TestClass::ParamType ParamType; - explicit ParameterizedTestFactory(ParamType parameter) : - parameter_(parameter) {} - Test* CreateTest() override { - TestClass::SetParam(¶meter_); - return new TestClass(); - } - - private: - const ParamType parameter_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); -}; - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// TestMetaFactoryBase is a base class for meta-factories that create -// test factories for passing into MakeAndRegisterTestInfo function. -template -class TestMetaFactoryBase { - public: - virtual ~TestMetaFactoryBase() {} - - virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; -}; - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// TestMetaFactory creates test factories for passing into -// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives -// ownership of test factory pointer, same factory object cannot be passed -// into that method twice. But ParameterizedTestSuiteInfo is going to call -// it for each Test/Parameter value combination. Thus it needs meta factory -// creator class. -template -class TestMetaFactory - : public TestMetaFactoryBase { - public: - using ParamType = typename TestSuite::ParamType; - - TestMetaFactory() {} - - TestFactoryBase* CreateTestFactory(ParamType parameter) override { - return new ParameterizedTestFactory(parameter); - } - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); -}; - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// ParameterizedTestSuiteInfoBase is a generic interface -// to ParameterizedTestSuiteInfo classes. ParameterizedTestSuiteInfoBase -// accumulates test information provided by TEST_P macro invocations -// and generators provided by INSTANTIATE_TEST_SUITE_P macro invocations -// and uses that information to register all resulting test instances -// in RegisterTests method. The ParameterizeTestSuiteRegistry class holds -// a collection of pointers to the ParameterizedTestSuiteInfo objects -// and calls RegisterTests() on each of them when asked. -class ParameterizedTestSuiteInfoBase { - public: - virtual ~ParameterizedTestSuiteInfoBase() {} - - // Base part of test suite name for display purposes. - virtual const std::string& GetTestSuiteName() const = 0; - // Test case id to verify identity. - virtual TypeId GetTestSuiteTypeId() const = 0; - // UnitTest class invokes this method to register tests in this - // test suite right before running them in RUN_ALL_TESTS macro. - // This method should not be called more than once on any single - // instance of a ParameterizedTestSuiteInfoBase derived class. - virtual void RegisterTests() = 0; - - protected: - ParameterizedTestSuiteInfoBase() {} - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase); -}; - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P -// macro invocations for a particular test suite and generators -// obtained from INSTANTIATE_TEST_SUITE_P macro invocations for that -// test suite. It registers tests with all values generated by all -// generators when asked. -template -class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase { - public: - // ParamType and GeneratorCreationFunc are private types but are required - // for declarations of public methods AddTestPattern() and - // AddTestSuiteInstantiation(). - using ParamType = typename TestSuite::ParamType; - // A function that returns an instance of appropriate generator type. - typedef ParamGenerator(GeneratorCreationFunc)(); - using ParamNameGeneratorFunc = std::string(const TestParamInfo&); - - explicit ParameterizedTestSuiteInfo(const char* name, - CodeLocation code_location) - : test_suite_name_(name), code_location_(code_location) {} - - // Test case base name for display purposes. - const std::string& GetTestSuiteName() const override { - return test_suite_name_; - } - // Test case id to verify identity. - TypeId GetTestSuiteTypeId() const override { return GetTypeId(); } - // TEST_P macro uses AddTestPattern() to record information - // about a single test in a LocalTestInfo structure. - // test_suite_name is the base name of the test suite (without invocation - // prefix). test_base_name is the name of an individual test without - // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is - // test suite base name and DoBar is test base name. - void AddTestPattern(const char* test_suite_name, const char* test_base_name, - TestMetaFactoryBase* meta_factory) { - tests_.push_back(std::shared_ptr( - new TestInfo(test_suite_name, test_base_name, meta_factory))); - } - // INSTANTIATE_TEST_SUITE_P macro uses AddGenerator() to record information - // about a generator. - int AddTestSuiteInstantiation(const std::string& instantiation_name, - GeneratorCreationFunc* func, - ParamNameGeneratorFunc* name_func, - const char* file, int line) { - instantiations_.push_back( - InstantiationInfo(instantiation_name, func, name_func, file, line)); - return 0; // Return value used only to run this method in namespace scope. - } - // UnitTest class invokes this method to register tests in this test suite - // test suites right before running tests in RUN_ALL_TESTS macro. - // This method should not be called more than once on any single - // instance of a ParameterizedTestSuiteInfoBase derived class. - // UnitTest has a guard to prevent from calling this method more than once. - void RegisterTests() override { - for (typename TestInfoContainer::iterator test_it = tests_.begin(); - test_it != tests_.end(); ++test_it) { - std::shared_ptr test_info = *test_it; - for (typename InstantiationContainer::iterator gen_it = - instantiations_.begin(); gen_it != instantiations_.end(); - ++gen_it) { - const std::string& instantiation_name = gen_it->name; - ParamGenerator generator((*gen_it->generator)()); - ParamNameGeneratorFunc* name_func = gen_it->name_func; - const char* file = gen_it->file; - int line = gen_it->line; - - std::string test_suite_name; - if ( !instantiation_name.empty() ) - test_suite_name = instantiation_name + "/"; - test_suite_name += test_info->test_suite_base_name; - - size_t i = 0; - std::set test_param_names; - for (typename ParamGenerator::iterator param_it = - generator.begin(); - param_it != generator.end(); ++param_it, ++i) { - Message test_name_stream; - - std::string param_name = name_func( - TestParamInfo(*param_it, i)); - - GTEST_CHECK_(IsValidParamName(param_name)) - << "Parameterized test name '" << param_name - << "' is invalid, in " << file - << " line " << line << std::endl; - - GTEST_CHECK_(test_param_names.count(param_name) == 0) - << "Duplicate parameterized test name '" << param_name - << "', in " << file << " line " << line << std::endl; - - test_param_names.insert(param_name); - - test_name_stream << test_info->test_base_name << "/" << param_name; - MakeAndRegisterTestInfo( - test_suite_name.c_str(), test_name_stream.GetString().c_str(), - nullptr, // No type parameter. - PrintToString(*param_it).c_str(), code_location_, - GetTestSuiteTypeId(), - SuiteApiResolver::GetSetUpCaseOrSuite(file, line), - SuiteApiResolver::GetTearDownCaseOrSuite(file, line), - test_info->test_meta_factory->CreateTestFactory(*param_it)); - } // for param_it - } // for gen_it - } // for test_it - } // RegisterTests - - private: - // LocalTestInfo structure keeps information about a single test registered - // with TEST_P macro. - struct TestInfo { - TestInfo(const char* a_test_suite_base_name, const char* a_test_base_name, - TestMetaFactoryBase* a_test_meta_factory) - : test_suite_base_name(a_test_suite_base_name), - test_base_name(a_test_base_name), - test_meta_factory(a_test_meta_factory) {} - - const std::string test_suite_base_name; - const std::string test_base_name; - const std::unique_ptr > test_meta_factory; - }; - using TestInfoContainer = ::std::vector >; - // Records data received from INSTANTIATE_TEST_SUITE_P macros: - // - struct InstantiationInfo { - InstantiationInfo(const std::string &name_in, - GeneratorCreationFunc* generator_in, - ParamNameGeneratorFunc* name_func_in, - const char* file_in, - int line_in) - : name(name_in), - generator(generator_in), - name_func(name_func_in), - file(file_in), - line(line_in) {} - - std::string name; - GeneratorCreationFunc* generator; - ParamNameGeneratorFunc* name_func; - const char* file; - int line; - }; - typedef ::std::vector InstantiationContainer; - - static bool IsValidParamName(const std::string& name) { - // Check for empty string - if (name.empty()) - return false; - - // Check for invalid characters - for (std::string::size_type index = 0; index < name.size(); ++index) { - if (!isalnum(name[index]) && name[index] != '_') - return false; - } - - return true; - } - - const std::string test_suite_name_; - CodeLocation code_location_; - TestInfoContainer tests_; - InstantiationContainer instantiations_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfo); -}; // class ParameterizedTestSuiteInfo - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -template -using ParameterizedTestCaseInfo = ParameterizedTestSuiteInfo; -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// ParameterizedTestSuiteRegistry contains a map of -// ParameterizedTestSuiteInfoBase classes accessed by test suite names. TEST_P -// and INSTANTIATE_TEST_SUITE_P macros use it to locate their corresponding -// ParameterizedTestSuiteInfo descriptors. -class ParameterizedTestSuiteRegistry { - public: - ParameterizedTestSuiteRegistry() {} - ~ParameterizedTestSuiteRegistry() { - for (auto& test_suite_info : test_suite_infos_) { - delete test_suite_info; - } - } - - // Looks up or creates and returns a structure containing information about - // tests and instantiations of a particular test suite. - template - ParameterizedTestSuiteInfo* GetTestSuitePatternHolder( - const char* test_suite_name, CodeLocation code_location) { - ParameterizedTestSuiteInfo* typed_test_info = nullptr; - for (auto& test_suite_info : test_suite_infos_) { - if (test_suite_info->GetTestSuiteName() == test_suite_name) { - if (test_suite_info->GetTestSuiteTypeId() != GetTypeId()) { - // Complain about incorrect usage of Google Test facilities - // and terminate the program since we cannot guaranty correct - // test suite setup and tear-down in this case. - ReportInvalidTestSuiteType(test_suite_name, code_location); - posix::Abort(); - } else { - // At this point we are sure that the object we found is of the same - // type we are looking for, so we downcast it to that type - // without further checks. - typed_test_info = CheckedDowncastToActualType< - ParameterizedTestSuiteInfo >(test_suite_info); - } - break; - } - } - if (typed_test_info == nullptr) { - typed_test_info = new ParameterizedTestSuiteInfo( - test_suite_name, code_location); - test_suite_infos_.push_back(typed_test_info); - } - return typed_test_info; - } - void RegisterTests() { - for (auto& test_suite_info : test_suite_infos_) { - test_suite_info->RegisterTests(); - } - } -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - template - ParameterizedTestCaseInfo* GetTestCasePatternHolder( - const char* test_case_name, CodeLocation code_location) { - return GetTestSuitePatternHolder(test_case_name, code_location); - } - -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - private: - using TestSuiteInfoContainer = ::std::vector; - - TestSuiteInfoContainer test_suite_infos_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteRegistry); -}; - -} // namespace internal - -// Forward declarations of ValuesIn(), which is implemented in -// include/gtest/gtest-param-test.h. -template -internal::ParamGenerator ValuesIn( - const Container& container); - -namespace internal { -// Used in the Values() function to provide polymorphic capabilities. - -template -class ValueArray { - public: - ValueArray(Ts... v) : v_{std::move(v)...} {} - - template - operator ParamGenerator() const { // NOLINT - return ValuesIn(MakeVector(MakeIndexSequence())); - } - - private: - template - std::vector MakeVector(IndexSequence) const { - return std::vector{static_cast(v_.template Get())...}; - } - - FlatTuple v_; -}; - -template -class CartesianProductGenerator - : public ParamGeneratorInterface<::std::tuple> { - public: - typedef ::std::tuple ParamType; - - CartesianProductGenerator(const std::tuple...>& g) - : generators_(g) {} - ~CartesianProductGenerator() override {} - - ParamIteratorInterface* Begin() const override { - return new Iterator(this, generators_, false); - } - ParamIteratorInterface* End() const override { - return new Iterator(this, generators_, true); - } - - private: - template - class IteratorImpl; - template - class IteratorImpl> - : public ParamIteratorInterface { - public: - IteratorImpl(const ParamGeneratorInterface* base, - const std::tuple...>& generators, bool is_end) - : base_(base), - begin_(std::get(generators).begin()...), - end_(std::get(generators).end()...), - current_(is_end ? end_ : begin_) { - ComputeCurrentValue(); - } - ~IteratorImpl() override {} - - const ParamGeneratorInterface* BaseGenerator() const override { - return base_; - } - // Advance should not be called on beyond-of-range iterators - // so no component iterators must be beyond end of range, either. - void Advance() override { - assert(!AtEnd()); - // Advance the last iterator. - ++std::get(current_); - // if that reaches end, propagate that up. - AdvanceIfEnd(); - ComputeCurrentValue(); - } - ParamIteratorInterface* Clone() const override { - return new IteratorImpl(*this); - } - - const ParamType* Current() const override { return current_value_.get(); } - - bool Equals(const ParamIteratorInterface& other) const override { - // Having the same base generator guarantees that the other - // iterator is of the same type and we can downcast. - GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) - << "The program attempted to compare iterators " - << "from different generators." << std::endl; - const IteratorImpl* typed_other = - CheckedDowncastToActualType(&other); - - // We must report iterators equal if they both point beyond their - // respective ranges. That can happen in a variety of fashions, - // so we have to consult AtEnd(). - if (AtEnd() && typed_other->AtEnd()) return true; - - bool same = true; - bool dummy[] = { - (same = same && std::get(current_) == - std::get(typed_other->current_))...}; - (void)dummy; - return same; - } - - private: - template - void AdvanceIfEnd() { - if (std::get(current_) != std::get(end_)) return; - - bool last = ThisI == 0; - if (last) { - // We are done. Nothing else to propagate. - return; - } - - constexpr size_t NextI = ThisI - (ThisI != 0); - std::get(current_) = std::get(begin_); - ++std::get(current_); - AdvanceIfEnd(); - } - - void ComputeCurrentValue() { - if (!AtEnd()) - current_value_ = std::make_shared(*std::get(current_)...); - } - bool AtEnd() const { - bool at_end = false; - bool dummy[] = { - (at_end = at_end || std::get(current_) == std::get(end_))...}; - (void)dummy; - return at_end; - } - - const ParamGeneratorInterface* const base_; - std::tuple::iterator...> begin_; - std::tuple::iterator...> end_; - std::tuple::iterator...> current_; - std::shared_ptr current_value_; - }; - - using Iterator = IteratorImpl::type>; - - std::tuple...> generators_; -}; - -template -class CartesianProductHolder { - public: - CartesianProductHolder(const Gen&... g) : generators_(g...) {} - template - operator ParamGenerator<::std::tuple>() const { - return ParamGenerator<::std::tuple>( - new CartesianProductGenerator(generators_)); - } - - private: - std::tuple generators_; -}; - -} // namespace internal -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ - -namespace testing { - -// Functions producing parameter generators. -// -// Google Test uses these generators to produce parameters for value- -// parameterized tests. When a parameterized test suite is instantiated -// with a particular generator, Google Test creates and runs tests -// for each element in the sequence produced by the generator. -// -// In the following sample, tests from test suite FooTest are instantiated -// each three times with parameter values 3, 5, and 8: -// -// class FooTest : public TestWithParam { ... }; -// -// TEST_P(FooTest, TestThis) { -// } -// TEST_P(FooTest, TestThat) { -// } -// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8)); -// - -// Range() returns generators providing sequences of values in a range. -// -// Synopsis: -// Range(start, end) -// - returns a generator producing a sequence of values {start, start+1, -// start+2, ..., }. -// Range(start, end, step) -// - returns a generator producing a sequence of values {start, start+step, -// start+step+step, ..., }. -// Notes: -// * The generated sequences never include end. For example, Range(1, 5) -// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) -// returns a generator producing {1, 3, 5, 7}. -// * start and end must have the same type. That type may be any integral or -// floating-point type or a user defined type satisfying these conditions: -// * It must be assignable (have operator=() defined). -// * It must have operator+() (operator+(int-compatible type) for -// two-operand version). -// * It must have operator<() defined. -// Elements in the resulting sequences will also have that type. -// * Condition start < end must be satisfied in order for resulting sequences -// to contain any elements. -// -template -internal::ParamGenerator Range(T start, T end, IncrementT step) { - return internal::ParamGenerator( - new internal::RangeGenerator(start, end, step)); -} - -template -internal::ParamGenerator Range(T start, T end) { - return Range(start, end, 1); -} - -// ValuesIn() function allows generation of tests with parameters coming from -// a container. -// -// Synopsis: -// ValuesIn(const T (&array)[N]) -// - returns a generator producing sequences with elements from -// a C-style array. -// ValuesIn(const Container& container) -// - returns a generator producing sequences with elements from -// an STL-style container. -// ValuesIn(Iterator begin, Iterator end) -// - returns a generator producing sequences with elements from -// a range [begin, end) defined by a pair of STL-style iterators. These -// iterators can also be plain C pointers. -// -// Please note that ValuesIn copies the values from the containers -// passed in and keeps them to generate tests in RUN_ALL_TESTS(). -// -// Examples: -// -// This instantiates tests from test suite StringTest -// each with C-string values of "foo", "bar", and "baz": -// -// const char* strings[] = {"foo", "bar", "baz"}; -// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings)); -// -// This instantiates tests from test suite StlStringTest -// each with STL strings with values "a" and "b": -// -// ::std::vector< ::std::string> GetParameterStrings() { -// ::std::vector< ::std::string> v; -// v.push_back("a"); -// v.push_back("b"); -// return v; -// } -// -// INSTANTIATE_TEST_SUITE_P(CharSequence, -// StlStringTest, -// ValuesIn(GetParameterStrings())); -// -// -// This will also instantiate tests from CharTest -// each with parameter values 'a' and 'b': -// -// ::std::list GetParameterChars() { -// ::std::list list; -// list.push_back('a'); -// list.push_back('b'); -// return list; -// } -// ::std::list l = GetParameterChars(); -// INSTANTIATE_TEST_SUITE_P(CharSequence2, -// CharTest, -// ValuesIn(l.begin(), l.end())); -// -template -internal::ParamGenerator< - typename ::testing::internal::IteratorTraits::value_type> -ValuesIn(ForwardIterator begin, ForwardIterator end) { - typedef typename ::testing::internal::IteratorTraits - ::value_type ParamType; - return internal::ParamGenerator( - new internal::ValuesInIteratorRangeGenerator(begin, end)); -} - -template -internal::ParamGenerator ValuesIn(const T (&array)[N]) { - return ValuesIn(array, array + N); -} - -template -internal::ParamGenerator ValuesIn( - const Container& container) { - return ValuesIn(container.begin(), container.end()); -} - -// Values() allows generating tests from explicitly specified list of -// parameters. -// -// Synopsis: -// Values(T v1, T v2, ..., T vN) -// - returns a generator producing sequences with elements v1, v2, ..., vN. -// -// For example, this instantiates tests from test suite BarTest each -// with values "one", "two", and "three": -// -// INSTANTIATE_TEST_SUITE_P(NumSequence, -// BarTest, -// Values("one", "two", "three")); -// -// This instantiates tests from test suite BazTest each with values 1, 2, 3.5. -// The exact type of values will depend on the type of parameter in BazTest. -// -// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); -// -// -template -internal::ValueArray Values(T... v) { - return internal::ValueArray(std::move(v)...); -} - -// Bool() allows generating tests with parameters in a set of (false, true). -// -// Synopsis: -// Bool() -// - returns a generator producing sequences with elements {false, true}. -// -// It is useful when testing code that depends on Boolean flags. Combinations -// of multiple flags can be tested when several Bool()'s are combined using -// Combine() function. -// -// In the following example all tests in the test suite FlagDependentTest -// will be instantiated twice with parameters false and true. -// -// class FlagDependentTest : public testing::TestWithParam { -// virtual void SetUp() { -// external_flag = GetParam(); -// } -// } -// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool()); -// -inline internal::ParamGenerator Bool() { - return Values(false, true); -} - -// Combine() allows the user to combine two or more sequences to produce -// values of a Cartesian product of those sequences' elements. -// -// Synopsis: -// Combine(gen1, gen2, ..., genN) -// - returns a generator producing sequences with elements coming from -// the Cartesian product of elements from the sequences generated by -// gen1, gen2, ..., genN. The sequence elements will have a type of -// std::tuple where T1, T2, ..., TN are the types -// of elements from sequences produces by gen1, gen2, ..., genN. -// -// Combine can have up to 10 arguments. -// -// Example: -// -// This will instantiate tests in test suite AnimalTest each one with -// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), -// tuple("dog", BLACK), and tuple("dog", WHITE): -// -// enum Color { BLACK, GRAY, WHITE }; -// class AnimalTest -// : public testing::TestWithParam > {...}; -// -// TEST_P(AnimalTest, AnimalLooksNice) {...} -// -// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest, -// Combine(Values("cat", "dog"), -// Values(BLACK, WHITE))); -// -// This will instantiate tests in FlagDependentTest with all variations of two -// Boolean flags: -// -// class FlagDependentTest -// : public testing::TestWithParam > { -// virtual void SetUp() { -// // Assigns external_flag_1 and external_flag_2 values from the tuple. -// std::tie(external_flag_1, external_flag_2) = GetParam(); -// } -// }; -// -// TEST_P(FlagDependentTest, TestFeature1) { -// // Test your code using external_flag_1 and external_flag_2 here. -// } -// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest, -// Combine(Bool(), Bool())); -// -template -internal::CartesianProductHolder Combine(const Generator&... g) { - return internal::CartesianProductHolder(g...); -} - -#define TEST_P(test_suite_name, test_name) \ - class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \ - : public test_suite_name { \ - public: \ - GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \ - virtual void TestBody(); \ - \ - private: \ - static int AddToRegistry() { \ - ::testing::UnitTest::GetInstance() \ - ->parameterized_test_registry() \ - .GetTestSuitePatternHolder( \ - #test_suite_name, \ - ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ - ->AddTestPattern( \ - GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), \ - new ::testing::internal::TestMetaFactory()); \ - return 0; \ - } \ - static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \ - GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, \ - test_name)); \ - }; \ - int GTEST_TEST_CLASS_NAME_(test_suite_name, \ - test_name)::gtest_registering_dummy_ = \ - GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); \ - void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() - -// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify -// generator and an optional function or functor that generates custom test name -// suffixes based on the test parameters. Such a function or functor should -// accept one argument of type testing::TestParamInfo, and -// return std::string. -// -// testing::PrintToStringParamName is a builtin test suffix generator that -// returns the value of testing::PrintToString(GetParam()). -// -// Note: test names must be non-empty, unique, and may only contain ASCII -// alphanumeric characters or underscore. Because PrintToString adds quotes -// to std::string and C strings, it won't work for these types. - -#define GTEST_EXPAND_(arg) arg -#define GTEST_GET_FIRST_(first, ...) first -#define GTEST_GET_SECOND_(first, second, ...) second - -#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) \ - static ::testing::internal::ParamGenerator \ - gtest_##prefix##test_suite_name##_EvalGenerator_() { \ - return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); \ - } \ - static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( \ - const ::testing::TestParamInfo& info) { \ - if (::testing::internal::AlwaysFalse()) { \ - ::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( \ - __VA_ARGS__, \ - ::testing::internal::DefaultParamName, \ - DUMMY_PARAM_))); \ - auto t = std::make_tuple(__VA_ARGS__); \ - static_assert(std::tuple_size::value <= 2, \ - "Too Many Args!"); \ - } \ - return ((GTEST_EXPAND_(GTEST_GET_SECOND_( \ - __VA_ARGS__, \ - ::testing::internal::DefaultParamName, \ - DUMMY_PARAM_))))(info); \ - } \ - static int gtest_##prefix##test_suite_name##_dummy_ \ - GTEST_ATTRIBUTE_UNUSED_ = \ - ::testing::UnitTest::GetInstance() \ - ->parameterized_test_registry() \ - .GetTestSuitePatternHolder( \ - #test_suite_name, \ - ::testing::internal::CodeLocation(__FILE__, __LINE__)) \ - ->AddTestSuiteInstantiation( \ - #prefix, >est_##prefix##test_suite_name##_EvalGenerator_, \ - >est_##prefix##test_suite_name##_EvalGenerateName_, \ - __FILE__, __LINE__) - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -#define INSTANTIATE_TEST_CASE_P \ - static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \ - ""); \ - INSTANTIATE_TEST_SUITE_P -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ -// Copyright 2006, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// -// Google C++ Testing and Mocking Framework definitions useful in production code. -// GOOGLETEST_CM0003 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_ -#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_ - -// When you need to test the private or protected members of a class, -// use the FRIEND_TEST macro to declare your tests as friends of the -// class. For example: -// -// class MyClass { -// private: -// void PrivateMethod(); -// FRIEND_TEST(MyClassTest, PrivateMethodWorks); -// }; -// -// class MyClassTest : public testing::Test { -// // ... -// }; -// -// TEST_F(MyClassTest, PrivateMethodWorks) { -// // Can call MyClass::PrivateMethod() here. -// } -// -// Note: The test class must be in the same namespace as the class being tested. -// For example, putting MyClassTest in an anonymous namespace will not work. - -#define FRIEND_TEST(test_case_name, test_name)\ -friend class test_case_name##_##test_name##_Test - -#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_ -// Copyright 2008, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ -#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ - -#include -#include - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -namespace testing { - -// A copyable object representing the result of a test part (i.e. an -// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). -// -// Don't inherit from TestPartResult as its destructor is not virtual. -class GTEST_API_ TestPartResult { - public: - // The possible outcomes of a test part (i.e. an assertion or an - // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). - enum Type { - kSuccess, // Succeeded. - kNonFatalFailure, // Failed but the test can continue. - kFatalFailure, // Failed and the test should be terminated. - kSkip // Skipped. - }; - - // C'tor. TestPartResult does NOT have a default constructor. - // Always use this constructor (with parameters) to create a - // TestPartResult object. - TestPartResult(Type a_type, const char* a_file_name, int a_line_number, - const char* a_message) - : type_(a_type), - file_name_(a_file_name == nullptr ? "" : a_file_name), - line_number_(a_line_number), - summary_(ExtractSummary(a_message)), - message_(a_message) {} - - // Gets the outcome of the test part. - Type type() const { return type_; } - - // Gets the name of the source file where the test part took place, or - // NULL if it's unknown. - const char* file_name() const { - return file_name_.empty() ? nullptr : file_name_.c_str(); - } - - // Gets the line in the source file where the test part took place, - // or -1 if it's unknown. - int line_number() const { return line_number_; } - - // Gets the summary of the failure message. - const char* summary() const { return summary_.c_str(); } - - // Gets the message associated with the test part. - const char* message() const { return message_.c_str(); } - - // Returns true iff the test part was skipped. - bool skipped() const { return type_ == kSkip; } - - // Returns true iff the test part passed. - bool passed() const { return type_ == kSuccess; } - - // Returns true iff the test part non-fatally failed. - bool nonfatally_failed() const { return type_ == kNonFatalFailure; } - - // Returns true iff the test part fatally failed. - bool fatally_failed() const { return type_ == kFatalFailure; } - - // Returns true iff the test part failed. - bool failed() const { return fatally_failed() || nonfatally_failed(); } - - private: - Type type_; - - // Gets the summary of the failure message by omitting the stack - // trace in it. - static std::string ExtractSummary(const char* message); - - // The name of the source file where the test part took place, or - // "" if the source file is unknown. - std::string file_name_; - // The line in the source file where the test part took place, or -1 - // if the line number is unknown. - int line_number_; - std::string summary_; // The test failure summary. - std::string message_; // The test failure message. -}; - -// Prints a TestPartResult object. -std::ostream& operator<<(std::ostream& os, const TestPartResult& result); - -// An array of TestPartResult objects. -// -// Don't inherit from TestPartResultArray as its destructor is not -// virtual. -class GTEST_API_ TestPartResultArray { - public: - TestPartResultArray() {} - - // Appends the given TestPartResult to the array. - void Append(const TestPartResult& result); - - // Returns the TestPartResult at the given index (0-based). - const TestPartResult& GetTestPartResult(int index) const; - - // Returns the number of TestPartResult objects in the array. - int size() const; - - private: - std::vector array_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); -}; - -// This interface knows how to report a test part result. -class GTEST_API_ TestPartResultReporterInterface { - public: - virtual ~TestPartResultReporterInterface() {} - - virtual void ReportTestPartResult(const TestPartResult& result) = 0; -}; - -namespace internal { - -// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a -// statement generates new fatal failures. To do so it registers itself as the -// current test part result reporter. Besides checking if fatal failures were -// reported, it only delegates the reporting to the former result reporter. -// The original result reporter is restored in the destructor. -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -class GTEST_API_ HasNewFatalFailureHelper - : public TestPartResultReporterInterface { - public: - HasNewFatalFailureHelper(); - ~HasNewFatalFailureHelper() override; - void ReportTestPartResult(const TestPartResult& result) override; - bool has_new_fatal_failure() const { return has_new_fatal_failure_; } - private: - bool has_new_fatal_failure_; - TestPartResultReporterInterface* original_reporter_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); -}; - -} // namespace internal - -} // namespace testing - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ -// Copyright 2008 Google Inc. -// All Rights Reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ -#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ - -// This header implements typed tests and type-parameterized tests. - -// Typed (aka type-driven) tests repeat the same test for types in a -// list. You must know which types you want to test with when writing -// typed tests. Here's how you do it: - -#if 0 - -// First, define a fixture class template. It should be parameterized -// by a type. Remember to derive it from testing::Test. -template -class FooTest : public testing::Test { - public: - ... - typedef std::list List; - static T shared_; - T value_; -}; - -// Next, associate a list of types with the test suite, which will be -// repeated for each type in the list. The typedef is necessary for -// the macro to parse correctly. -typedef testing::Types MyTypes; -TYPED_TEST_SUITE(FooTest, MyTypes); - -// If the type list contains only one type, you can write that type -// directly without Types<...>: -// TYPED_TEST_SUITE(FooTest, int); - -// Then, use TYPED_TEST() instead of TEST_F() to define as many typed -// tests for this test suite as you want. -TYPED_TEST(FooTest, DoesBlah) { - // Inside a test, refer to TypeParam to get the type parameter. - // Since we are inside a derived class template, C++ requires use to - // visit the members of FooTest via 'this'. - TypeParam n = this->value_; - - // To visit static members of the fixture, add the TestFixture:: - // prefix. - n += TestFixture::shared_; - - // To refer to typedefs in the fixture, add the "typename - // TestFixture::" prefix. - typename TestFixture::List values; - values.push_back(n); - ... -} - -TYPED_TEST(FooTest, HasPropertyA) { ... } - -// TYPED_TEST_SUITE takes an optional third argument which allows to specify a -// class that generates custom test name suffixes based on the type. This should -// be a class which has a static template function GetName(int index) returning -// a string for each type. The provided integer index equals the index of the -// type in the provided type list. In many cases the index can be ignored. -// -// For example: -// class MyTypeNames { -// public: -// template -// static std::string GetName(int) { -// if (std::is_same()) return "char"; -// if (std::is_same()) return "int"; -// if (std::is_same()) return "unsignedInt"; -// } -// }; -// TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames); - -#endif // 0 - -// Type-parameterized tests are abstract test patterns parameterized -// by a type. Compared with typed tests, type-parameterized tests -// allow you to define the test pattern without knowing what the type -// parameters are. The defined pattern can be instantiated with -// different types any number of times, in any number of translation -// units. -// -// If you are designing an interface or concept, you can define a -// suite of type-parameterized tests to verify properties that any -// valid implementation of the interface/concept should have. Then, -// each implementation can easily instantiate the test suite to verify -// that it conforms to the requirements, without having to write -// similar tests repeatedly. Here's an example: - -#if 0 - -// First, define a fixture class template. It should be parameterized -// by a type. Remember to derive it from testing::Test. -template -class FooTest : public testing::Test { - ... -}; - -// Next, declare that you will define a type-parameterized test suite -// (the _P suffix is for "parameterized" or "pattern", whichever you -// prefer): -TYPED_TEST_SUITE_P(FooTest); - -// Then, use TYPED_TEST_P() to define as many type-parameterized tests -// for this type-parameterized test suite as you want. -TYPED_TEST_P(FooTest, DoesBlah) { - // Inside a test, refer to TypeParam to get the type parameter. - TypeParam n = 0; - ... -} - -TYPED_TEST_P(FooTest, HasPropertyA) { ... } - -// Now the tricky part: you need to register all test patterns before -// you can instantiate them. The first argument of the macro is the -// test suite name; the rest are the names of the tests in this test -// case. -REGISTER_TYPED_TEST_SUITE_P(FooTest, - DoesBlah, HasPropertyA); - -// Finally, you are free to instantiate the pattern with the types you -// want. If you put the above code in a header file, you can #include -// it in multiple C++ source files and instantiate it multiple times. -// -// To distinguish different instances of the pattern, the first -// argument to the INSTANTIATE_* macro is a prefix that will be added -// to the actual test suite name. Remember to pick unique prefixes for -// different instances. -typedef testing::Types MyTypes; -INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes); - -// If the type list contains only one type, you can write that type -// directly without Types<...>: -// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int); -// -// Similar to the optional argument of TYPED_TEST_SUITE above, -// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to -// generate custom names. -// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames); - -#endif // 0 - - -// Implements typed tests. - -#if GTEST_HAS_TYPED_TEST - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Expands to the name of the typedef for the type parameters of the -// given test suite. -#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_ - -// Expands to the name of the typedef for the NameGenerator, responsible for -// creating the suffixes of the name. -#define GTEST_NAME_GENERATOR_(TestSuiteName) \ - gtest_type_params_##TestSuiteName##_NameGenerator - -// The 'Types' template argument below must have spaces around it -// since some compilers may choke on '>>' when passing a template -// instance (e.g. Types) -#define TYPED_TEST_SUITE(CaseName, Types, ...) \ - typedef ::testing::internal::TypeList::type GTEST_TYPE_PARAMS_( \ - CaseName); \ - typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \ - GTEST_NAME_GENERATOR_(CaseName) - -# define TYPED_TEST(CaseName, TestName) \ - template \ - class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ - : public CaseName { \ - private: \ - typedef CaseName TestFixture; \ - typedef gtest_TypeParam_ TypeParam; \ - virtual void TestBody(); \ - }; \ - static bool gtest_##CaseName##_##TestName##_registered_ \ - GTEST_ATTRIBUTE_UNUSED_ = \ - ::testing::internal::TypeParameterizedTest< \ - CaseName, \ - ::testing::internal::TemplateSel, \ - GTEST_TYPE_PARAMS_( \ - CaseName)>::Register("", \ - ::testing::internal::CodeLocation( \ - __FILE__, __LINE__), \ - #CaseName, #TestName, 0, \ - ::testing::internal::GenerateNames< \ - GTEST_NAME_GENERATOR_(CaseName), \ - GTEST_TYPE_PARAMS_(CaseName)>()); \ - template \ - void GTEST_TEST_CLASS_NAME_(CaseName, \ - TestName)::TestBody() - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -#define TYPED_TEST_CASE \ - static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \ - TYPED_TEST_SUITE -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -#endif // GTEST_HAS_TYPED_TEST - -// Implements type-parameterized tests. - -#if GTEST_HAS_TYPED_TEST_P - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Expands to the namespace name that the type-parameterized tests for -// the given type-parameterized test suite are defined in. The exact -// name of the namespace is subject to change without notice. -#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_ - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// -// Expands to the name of the variable used to remember the names of -// the defined tests in the given test suite. -#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \ - gtest_typed_test_suite_p_state_##TestSuiteName##_ - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. -// -// Expands to the name of the variable used to remember the names of -// the registered tests in the given test suite. -#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \ - gtest_registered_test_names_##TestSuiteName##_ - -// The variables defined in the type-parameterized test macros are -// static as typically these macros are used in a .h file that can be -// #included in multiple translation units linked together. -#define TYPED_TEST_SUITE_P(SuiteName) \ - static ::testing::internal::TypedTestSuitePState \ - GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName) - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -#define TYPED_TEST_CASE_P \ - static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \ - TYPED_TEST_SUITE_P -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -#define TYPED_TEST_P(SuiteName, TestName) \ - namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ - template \ - class TestName : public SuiteName { \ - private: \ - typedef SuiteName TestFixture; \ - typedef gtest_TypeParam_ TypeParam; \ - virtual void TestBody(); \ - }; \ - static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \ - GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \ - __FILE__, __LINE__, #SuiteName, #TestName); \ - } \ - template \ - void GTEST_SUITE_NAMESPACE_( \ - SuiteName)::TestName::TestBody() - -#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...) \ - namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \ - typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ - } \ - static const char* const GTEST_REGISTERED_TEST_NAMES_( \ - SuiteName) GTEST_ATTRIBUTE_UNUSED_ = \ - GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \ - __FILE__, __LINE__, #__VA_ARGS__) - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -#define REGISTER_TYPED_TEST_CASE_P \ - static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \ - ""); \ - REGISTER_TYPED_TEST_SUITE_P -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -// The 'Types' template argument below must have spaces around it -// since some compilers may choke on '>>' when passing a template -// instance (e.g. Types) -#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...) \ - static bool gtest_##Prefix##_##SuiteName GTEST_ATTRIBUTE_UNUSED_ = \ - ::testing::internal::TypeParameterizedTestSuite< \ - SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_, \ - ::testing::internal::TypeList::type>:: \ - Register(#Prefix, \ - ::testing::internal::CodeLocation(__FILE__, __LINE__), \ - >EST_TYPED_TEST_SUITE_P_STATE_(SuiteName), #SuiteName, \ - GTEST_REGISTERED_TEST_NAMES_(SuiteName), \ - ::testing::internal::GenerateNames< \ - ::testing::internal::NameGeneratorSelector< \ - __VA_ARGS__>::type, \ - ::testing::internal::TypeList::type>()) - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -#define INSTANTIATE_TYPED_TEST_CASE_P \ - static_assert( \ - ::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \ - INSTANTIATE_TYPED_TEST_SUITE_P -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - -#endif // GTEST_HAS_TYPED_TEST_P - -#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ - -GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \ -/* class A needs to have dll-interface to be used by clients of class B */) - -namespace testing { - -// Silence C4100 (unreferenced formal parameter) and 4805 -// unsafe mix of type 'const int' and type 'const bool' -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable:4805) -# pragma warning(disable:4100) -#endif - - -// Declares the flags. - -// This flag temporary enables the disabled tests. -GTEST_DECLARE_bool_(also_run_disabled_tests); - -// This flag brings the debugger on an assertion failure. -GTEST_DECLARE_bool_(break_on_failure); - -// This flag controls whether Google Test catches all test-thrown exceptions -// and logs them as failures. -GTEST_DECLARE_bool_(catch_exceptions); - -// This flag enables using colors in terminal output. Available values are -// "yes" to enable colors, "no" (disable colors), or "auto" (the default) -// to let Google Test decide. -GTEST_DECLARE_string_(color); - -// This flag sets up the filter to select by name using a glob pattern -// the tests to run. If the filter is not given all tests are executed. -GTEST_DECLARE_string_(filter); - -// This flag controls whether Google Test installs a signal handler that dumps -// debugging information when fatal signals are raised. -GTEST_DECLARE_bool_(install_failure_signal_handler); - -// This flag causes the Google Test to list tests. None of the tests listed -// are actually run if the flag is provided. -GTEST_DECLARE_bool_(list_tests); - -// This flag controls whether Google Test emits a detailed XML report to a file -// in addition to its normal textual output. -GTEST_DECLARE_string_(output); - -// This flags control whether Google Test prints the elapsed time for each -// test. -GTEST_DECLARE_bool_(print_time); - -// This flags control whether Google Test prints UTF8 characters as text. -GTEST_DECLARE_bool_(print_utf8); - -// This flag specifies the random number seed. -GTEST_DECLARE_int32_(random_seed); - -// This flag sets how many times the tests are repeated. The default value -// is 1. If the value is -1 the tests are repeating forever. -GTEST_DECLARE_int32_(repeat); - -// This flag controls whether Google Test includes Google Test internal -// stack frames in failure stack traces. -GTEST_DECLARE_bool_(show_internal_stack_frames); - -// When this flag is specified, tests' order is randomized on every iteration. -GTEST_DECLARE_bool_(shuffle); - -// This flag specifies the maximum number of stack frames to be -// printed in a failure message. -GTEST_DECLARE_int32_(stack_trace_depth); - -// When this flag is specified, a failed assertion will throw an -// exception if exceptions are enabled, or exit the program with a -// non-zero code otherwise. For use with an external test framework. -GTEST_DECLARE_bool_(throw_on_failure); - -// When this flag is set with a "host:port" string, on supported -// platforms test results are streamed to the specified port on -// the specified host machine. -GTEST_DECLARE_string_(stream_result_to); - -#if GTEST_USE_OWN_FLAGFILE_FLAG_ -GTEST_DECLARE_string_(flagfile); -#endif // GTEST_USE_OWN_FLAGFILE_FLAG_ - -// The upper limit for valid stack trace depths. -const int kMaxStackTraceDepth = 100; - -namespace internal { - -class AssertHelper; -class DefaultGlobalTestPartResultReporter; -class ExecDeathTest; -class NoExecDeathTest; -class FinalSuccessChecker; -class GTestFlagSaver; -class StreamingListenerTest; -class TestResultAccessor; -class TestEventListenersAccessor; -class TestEventRepeater; -class UnitTestRecordPropertyTestHelper; -class WindowsDeathTest; -class FuchsiaDeathTest; -class UnitTestImpl* GetUnitTestImpl(); -void ReportFailureInUnknownLocation(TestPartResult::Type result_type, - const std::string& message); - -} // namespace internal - -// The friend relationship of some of these classes is cyclic. -// If we don't forward declare them the compiler might confuse the classes -// in friendship clauses with same named classes on the scope. -class Test; -class TestSuite; - -// Old API is still available but deprecated -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ -using TestCase = TestSuite; -#endif -class TestInfo; -class UnitTest; - -// A class for indicating whether an assertion was successful. When -// the assertion wasn't successful, the AssertionResult object -// remembers a non-empty message that describes how it failed. -// -// To create an instance of this class, use one of the factory functions -// (AssertionSuccess() and AssertionFailure()). -// -// This class is useful for two purposes: -// 1. Defining predicate functions to be used with Boolean test assertions -// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts -// 2. Defining predicate-format functions to be -// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). -// -// For example, if you define IsEven predicate: -// -// testing::AssertionResult IsEven(int n) { -// if ((n % 2) == 0) -// return testing::AssertionSuccess(); -// else -// return testing::AssertionFailure() << n << " is odd"; -// } -// -// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) -// will print the message -// -// Value of: IsEven(Fib(5)) -// Actual: false (5 is odd) -// Expected: true -// -// instead of a more opaque -// -// Value of: IsEven(Fib(5)) -// Actual: false -// Expected: true -// -// in case IsEven is a simple Boolean predicate. -// -// If you expect your predicate to be reused and want to support informative -// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up -// about half as often as positive ones in our tests), supply messages for -// both success and failure cases: -// -// testing::AssertionResult IsEven(int n) { -// if ((n % 2) == 0) -// return testing::AssertionSuccess() << n << " is even"; -// else -// return testing::AssertionFailure() << n << " is odd"; -// } -// -// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print -// -// Value of: IsEven(Fib(6)) -// Actual: true (8 is even) -// Expected: false -// -// NB: Predicates that support negative Boolean assertions have reduced -// performance in positive ones so be careful not to use them in tests -// that have lots (tens of thousands) of positive Boolean assertions. -// -// To use this class with EXPECT_PRED_FORMAT assertions such as: -// -// // Verifies that Foo() returns an even number. -// EXPECT_PRED_FORMAT1(IsEven, Foo()); -// -// you need to define: -// -// testing::AssertionResult IsEven(const char* expr, int n) { -// if ((n % 2) == 0) -// return testing::AssertionSuccess(); -// else -// return testing::AssertionFailure() -// << "Expected: " << expr << " is even\n Actual: it's " << n; -// } -// -// If Foo() returns 5, you will see the following message: -// -// Expected: Foo() is even -// Actual: it's 5 -// -class GTEST_API_ AssertionResult { - public: - // Copy constructor. - // Used in EXPECT_TRUE/FALSE(assertion_result). - AssertionResult(const AssertionResult& other); - -#if defined(_MSC_VER) && _MSC_VER < 1910 - GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */) -#endif - - // Used in the EXPECT_TRUE/FALSE(bool_expression). - // - // T must be contextually convertible to bool. - // - // The second parameter prevents this overload from being considered if - // the argument is implicitly convertible to AssertionResult. In that case - // we want AssertionResult's copy constructor to be used. - template - explicit AssertionResult( - const T& success, - typename internal::EnableIf< - !std::is_convertible::value>::type* - /*enabler*/ - = nullptr) - : success_(success) {} - -#if defined(_MSC_VER) && _MSC_VER < 1910 - GTEST_DISABLE_MSC_WARNINGS_POP_() -#endif - - // Assignment operator. - AssertionResult& operator=(AssertionResult other) { - swap(other); - return *this; - } - - // Returns true iff the assertion succeeded. - operator bool() const { return success_; } // NOLINT - - // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. - AssertionResult operator!() const; - - // Returns the text streamed into this AssertionResult. Test assertions - // use it when they fail (i.e., the predicate's outcome doesn't match the - // assertion's expectation). When nothing has been streamed into the - // object, returns an empty string. - const char* message() const { - return message_.get() != nullptr ? message_->c_str() : ""; - } - // Deprecated; please use message() instead. - const char* failure_message() const { return message(); } - - // Streams a custom failure message into this object. - template AssertionResult& operator<<(const T& value) { - AppendMessage(Message() << value); - return *this; - } - - // Allows streaming basic output manipulators such as endl or flush into - // this object. - AssertionResult& operator<<( - ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { - AppendMessage(Message() << basic_manipulator); - return *this; - } - - private: - // Appends the contents of message to message_. - void AppendMessage(const Message& a_message) { - if (message_.get() == nullptr) message_.reset(new ::std::string); - message_->append(a_message.GetString().c_str()); - } - - // Swap the contents of this AssertionResult with other. - void swap(AssertionResult& other); - - // Stores result of the assertion predicate. - bool success_; - // Stores the message describing the condition in case the expectation - // construct is not satisfied with the predicate's outcome. - // Referenced via a pointer to avoid taking too much stack frame space - // with test assertions. - std::unique_ptr< ::std::string> message_; -}; - -// Makes a successful assertion result. -GTEST_API_ AssertionResult AssertionSuccess(); - -// Makes a failed assertion result. -GTEST_API_ AssertionResult AssertionFailure(); - -// Makes a failed assertion result with the given failure message. -// Deprecated; use AssertionFailure() << msg. -GTEST_API_ AssertionResult AssertionFailure(const Message& msg); - -} // namespace testing - -// Includes the auto-generated header that implements a family of generic -// predicate assertion macros. This include comes late because it relies on -// APIs declared above. -// Copyright 2006, Google Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This file is AUTOMATICALLY GENERATED on 01/02/2019 by command -// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! -// -// Implements a family of generic predicate assertion macros. -// GOOGLETEST_CM0001 DO NOT DELETE - -#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ -#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ - - -namespace testing { - -// This header implements a family of generic predicate assertion -// macros: -// -// ASSERT_PRED_FORMAT1(pred_format, v1) -// ASSERT_PRED_FORMAT2(pred_format, v1, v2) -// ... -// -// where pred_format is a function or functor that takes n (in the -// case of ASSERT_PRED_FORMATn) values and their source expression -// text, and returns a testing::AssertionResult. See the definition -// of ASSERT_EQ in gtest.h for an example. -// -// If you don't care about formatting, you can use the more -// restrictive version: -// -// ASSERT_PRED1(pred, v1) -// ASSERT_PRED2(pred, v1, v2) -// ... -// -// where pred is an n-ary function or functor that returns bool, -// and the values v1, v2, ..., must support the << operator for -// streaming to std::ostream. -// -// We also define the EXPECT_* variations. -// -// For now we only support predicates whose arity is at most 5. -// Please email googletestframework@googlegroups.com if you need -// support for higher arities. - -// GTEST_ASSERT_ is the basic statement to which all of the assertions -// in this file reduce. Don't use this in your code. - -#define GTEST_ASSERT_(expression, on_failure) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if (const ::testing::AssertionResult gtest_ar = (expression)) \ - ; \ - else \ - on_failure(gtest_ar.failure_message()) - - -// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use -// this in your code. -template -AssertionResult AssertPred1Helper(const char* pred_text, - const char* e1, - Pred pred, - const T1& v1) { - if (pred(v1)) return AssertionSuccess(); - - return AssertionFailure() - << pred_text << "(" << e1 << ") evaluates to false, where" - << "\n" - << e1 << " evaluates to " << ::testing::PrintToString(v1); -} - -// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. -// Don't use this in your code. -#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ - GTEST_ASSERT_(pred_format(#v1, v1), \ - on_failure) - -// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use -// this in your code. -#define GTEST_PRED1_(pred, v1, on_failure)\ - GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ - #v1, \ - pred, \ - v1), on_failure) - -// Unary predicate assertion macros. -#define EXPECT_PRED_FORMAT1(pred_format, v1) \ - GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) -#define EXPECT_PRED1(pred, v1) \ - GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) -#define ASSERT_PRED_FORMAT1(pred_format, v1) \ - GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) -#define ASSERT_PRED1(pred, v1) \ - GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) - - - -// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use -// this in your code. -template -AssertionResult AssertPred2Helper(const char* pred_text, - const char* e1, - const char* e2, - Pred pred, - const T1& v1, - const T2& v2) { - if (pred(v1, v2)) return AssertionSuccess(); - - return AssertionFailure() - << pred_text << "(" << e1 << ", " << e2 - << ") evaluates to false, where" - << "\n" - << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" - << e2 << " evaluates to " << ::testing::PrintToString(v2); -} - -// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. -// Don't use this in your code. -#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ - GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \ - on_failure) - -// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use -// this in your code. -#define GTEST_PRED2_(pred, v1, v2, on_failure)\ - GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ - #v1, \ - #v2, \ - pred, \ - v1, \ - v2), on_failure) - -// Binary predicate assertion macros. -#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ - GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) -#define EXPECT_PRED2(pred, v1, v2) \ - GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) -#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ - GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) -#define ASSERT_PRED2(pred, v1, v2) \ - GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) - - - -// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use -// this in your code. -template -AssertionResult AssertPred3Helper(const char* pred_text, - const char* e1, - const char* e2, - const char* e3, - Pred pred, - const T1& v1, - const T2& v2, - const T3& v3) { - if (pred(v1, v2, v3)) return AssertionSuccess(); - - return AssertionFailure() - << pred_text << "(" << e1 << ", " << e2 << ", " << e3 - << ") evaluates to false, where" - << "\n" - << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" - << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" - << e3 << " evaluates to " << ::testing::PrintToString(v3); -} - -// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. -// Don't use this in your code. -#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ - GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \ - on_failure) - -// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use -// this in your code. -#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ - GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ - #v1, \ - #v2, \ - #v3, \ - pred, \ - v1, \ - v2, \ - v3), on_failure) - -// Ternary predicate assertion macros. -#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ - GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) -#define EXPECT_PRED3(pred, v1, v2, v3) \ - GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) -#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ - GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) -#define ASSERT_PRED3(pred, v1, v2, v3) \ - GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) - - - -// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use -// this in your code. -template -AssertionResult AssertPred4Helper(const char* pred_text, - const char* e1, - const char* e2, - const char* e3, - const char* e4, - Pred pred, - const T1& v1, - const T2& v2, - const T3& v3, - const T4& v4) { - if (pred(v1, v2, v3, v4)) return AssertionSuccess(); - - return AssertionFailure() - << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 - << ") evaluates to false, where" - << "\n" - << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" - << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" - << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" - << e4 << " evaluates to " << ::testing::PrintToString(v4); -} - -// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. -// Don't use this in your code. -#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ - GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \ - on_failure) - -// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use -// this in your code. -#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ - GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ - #v1, \ - #v2, \ - #v3, \ - #v4, \ - pred, \ - v1, \ - v2, \ - v3, \ - v4), on_failure) - -// 4-ary predicate assertion macros. -#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ - GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) -#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ - GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) -#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ - GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) -#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ - GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) - - - -// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use -// this in your code. -template -AssertionResult AssertPred5Helper(const char* pred_text, - const char* e1, - const char* e2, - const char* e3, - const char* e4, - const char* e5, - Pred pred, - const T1& v1, - const T2& v2, - const T3& v3, - const T4& v4, - const T5& v5) { - if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); - - return AssertionFailure() - << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4 - << ", " << e5 << ") evaluates to false, where" - << "\n" - << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n" - << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n" - << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n" - << e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n" - << e5 << " evaluates to " << ::testing::PrintToString(v5); -} - -// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. -// Don't use this in your code. -#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ - GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \ - on_failure) - -// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use -// this in your code. -#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ - GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ - #v1, \ - #v2, \ - #v3, \ - #v4, \ - #v5, \ - pred, \ - v1, \ - v2, \ - v3, \ - v4, \ - v5), on_failure) - -// 5-ary predicate assertion macros. -#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ - GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) -#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ - GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) -#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ - GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) -#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ - GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) - - - -} // namespace testing - -#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ - -namespace testing { - -// The abstract class that all tests inherit from. -// -// In Google Test, a unit test program contains one or many TestSuites, and -// each TestSuite contains one or many Tests. -// -// When you define a test using the TEST macro, you don't need to -// explicitly derive from Test - the TEST macro automatically does -// this for you. -// -// The only time you derive from Test is when defining a test fixture -// to be used in a TEST_F. For example: -// -// class FooTest : public testing::Test { -// protected: -// void SetUp() override { ... } -// void TearDown() override { ... } -// ... -// }; -// -// TEST_F(FooTest, Bar) { ... } -// TEST_F(FooTest, Baz) { ... } -// -// Test is not copyable. -class GTEST_API_ Test { - public: - friend class TestInfo; - - // The d'tor is virtual as we intend to inherit from Test. - virtual ~Test(); - - // Sets up the stuff shared by all tests in this test case. - // - // Google Test will call Foo::SetUpTestSuite() before running the first - // test in test case Foo. Hence a sub-class can define its own - // SetUpTestSuite() method to shadow the one defined in the super - // class. - static void SetUpTestSuite() {} - - // Tears down the stuff shared by all tests in this test case. - // - // Google Test will call Foo::TearDownTestSuite() after running the last - // test in test case Foo. Hence a sub-class can define its own - // TearDownTestSuite() method to shadow the one defined in the super - // class. - static void TearDownTestSuite() {} - - // Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - static void TearDownTestCase() {} - static void SetUpTestCase() {} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Returns true iff the current test has a fatal failure. - static bool HasFatalFailure(); - - // Returns true iff the current test has a non-fatal failure. - static bool HasNonfatalFailure(); - - // Returns true iff the current test was skipped. - static bool IsSkipped(); - - // Returns true iff the current test has a (either fatal or - // non-fatal) failure. - static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } - - // Logs a property for the current test, test suite, or for the entire - // invocation of the test program when used outside of the context of a - // test suite. Only the last value for a given key is remembered. These - // are public static so they can be called from utility functions that are - // not members of the test fixture. Calls to RecordProperty made during - // lifespan of the test (from the moment its constructor starts to the - // moment its destructor finishes) will be output in XML as attributes of - // the element. Properties recorded from fixture's - // SetUpTestSuite or TearDownTestSuite are logged as attributes of the - // corresponding element. Calls to RecordProperty made in the - // global context (before or after invocation of RUN_ALL_TESTS and from - // SetUp/TearDown method of Environment objects registered with Google - // Test) will be output as attributes of the element. - static void RecordProperty(const std::string& key, const std::string& value); - static void RecordProperty(const std::string& key, int value); - - protected: - // Creates a Test object. - Test(); - - // Sets up the test fixture. - virtual void SetUp(); - - // Tears down the test fixture. - virtual void TearDown(); - - private: - // Returns true iff the current test has the same fixture class as - // the first test in the current test suite. - static bool HasSameFixtureClass(); - - // Runs the test after the test fixture has been set up. - // - // A sub-class must implement this to define the test logic. - // - // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. - // Instead, use the TEST or TEST_F macro. - virtual void TestBody() = 0; - - // Sets up, executes, and tears down the test. - void Run(); - - // Deletes self. We deliberately pick an unusual name for this - // internal method to avoid clashing with names used in user TESTs. - void DeleteSelf_() { delete this; } - - const std::unique_ptr gtest_flag_saver_; - - // Often a user misspells SetUp() as Setup() and spends a long time - // wondering why it is never called by Google Test. The declaration of - // the following method is solely for catching such an error at - // compile time: - // - // - The return type is deliberately chosen to be not void, so it - // will be a conflict if void Setup() is declared in the user's - // test fixture. - // - // - This method is private, so it will be another compiler error - // if the method is called from the user's test fixture. - // - // DO NOT OVERRIDE THIS FUNCTION. - // - // If you see an error about overriding the following function or - // about it being private, you have mis-spelled SetUp() as Setup(). - struct Setup_should_be_spelled_SetUp {}; - virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } - - // We disallow copying Tests. - GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); -}; - -typedef internal::TimeInMillis TimeInMillis; - -// A copyable object representing a user specified test property which can be -// output as a key/value string pair. -// -// Don't inherit from TestProperty as its destructor is not virtual. -class TestProperty { - public: - // C'tor. TestProperty does NOT have a default constructor. - // Always use this constructor (with parameters) to create a - // TestProperty object. - TestProperty(const std::string& a_key, const std::string& a_value) : - key_(a_key), value_(a_value) { - } - - // Gets the user supplied key. - const char* key() const { - return key_.c_str(); - } - - // Gets the user supplied value. - const char* value() const { - return value_.c_str(); - } - - // Sets a new value, overriding the one supplied in the constructor. - void SetValue(const std::string& new_value) { - value_ = new_value; - } - - private: - // The key supplied by the user. - std::string key_; - // The value supplied by the user. - std::string value_; -}; - -// The result of a single Test. This includes a list of -// TestPartResults, a list of TestProperties, a count of how many -// death tests there are in the Test, and how much time it took to run -// the Test. -// -// TestResult is not copyable. -class GTEST_API_ TestResult { - public: - // Creates an empty TestResult. - TestResult(); - - // D'tor. Do not inherit from TestResult. - ~TestResult(); - - // Gets the number of all test parts. This is the sum of the number - // of successful test parts and the number of failed test parts. - int total_part_count() const; - - // Returns the number of the test properties. - int test_property_count() const; - - // Returns true iff the test passed (i.e. no test part failed). - bool Passed() const { return !Skipped() && !Failed(); } - - // Returns true iff the test was skipped. - bool Skipped() const; - - // Returns true iff the test failed. - bool Failed() const; - - // Returns true iff the test fatally failed. - bool HasFatalFailure() const; - - // Returns true iff the test has a non-fatal failure. - bool HasNonfatalFailure() const; - - // Returns the elapsed time, in milliseconds. - TimeInMillis elapsed_time() const { return elapsed_time_; } - - // Returns the i-th test part result among all the results. i can range from 0 - // to total_part_count() - 1. If i is not in that range, aborts the program. - const TestPartResult& GetTestPartResult(int i) const; - - // Returns the i-th test property. i can range from 0 to - // test_property_count() - 1. If i is not in that range, aborts the - // program. - const TestProperty& GetTestProperty(int i) const; - - private: - friend class TestInfo; - friend class TestSuite; - friend class UnitTest; - friend class internal::DefaultGlobalTestPartResultReporter; - friend class internal::ExecDeathTest; - friend class internal::TestResultAccessor; - friend class internal::UnitTestImpl; - friend class internal::WindowsDeathTest; - friend class internal::FuchsiaDeathTest; - - // Gets the vector of TestPartResults. - const std::vector& test_part_results() const { - return test_part_results_; - } - - // Gets the vector of TestProperties. - const std::vector& test_properties() const { - return test_properties_; - } - - // Sets the elapsed time. - void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } - - // Adds a test property to the list. The property is validated and may add - // a non-fatal failure if invalid (e.g., if it conflicts with reserved - // key names). If a property is already recorded for the same key, the - // value will be updated, rather than storing multiple values for the same - // key. xml_element specifies the element for which the property is being - // recorded and is used for validation. - void RecordProperty(const std::string& xml_element, - const TestProperty& test_property); - - // Adds a failure if the key is a reserved attribute of Google Test - // testsuite tags. Returns true if the property is valid. - // FIXME: Validate attribute names are legal and human readable. - static bool ValidateTestProperty(const std::string& xml_element, - const TestProperty& test_property); - - // Adds a test part result to the list. - void AddTestPartResult(const TestPartResult& test_part_result); - - // Returns the death test count. - int death_test_count() const { return death_test_count_; } - - // Increments the death test count, returning the new count. - int increment_death_test_count() { return ++death_test_count_; } - - // Clears the test part results. - void ClearTestPartResults(); - - // Clears the object. - void Clear(); - - // Protects mutable state of the property vector and of owned - // properties, whose values may be updated. - internal::Mutex test_properites_mutex_; - - // The vector of TestPartResults - std::vector test_part_results_; - // The vector of TestProperties - std::vector test_properties_; - // Running count of death tests. - int death_test_count_; - // The elapsed time, in milliseconds. - TimeInMillis elapsed_time_; - - // We disallow copying TestResult. - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult); -}; // class TestResult - -// A TestInfo object stores the following information about a test: -// -// Test suite name -// Test name -// Whether the test should be run -// A function pointer that creates the test object when invoked -// Test result -// -// The constructor of TestInfo registers itself with the UnitTest -// singleton such that the RUN_ALL_TESTS() macro knows which tests to -// run. -class GTEST_API_ TestInfo { - public: - // Destructs a TestInfo object. This function is not virtual, so - // don't inherit from TestInfo. - ~TestInfo(); - - // Returns the test suite name. - const char* test_suite_name() const { return test_suite_name_.c_str(); } - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - const char* test_case_name() const { return test_suite_name(); } -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Returns the test name. - const char* name() const { return name_.c_str(); } - - // Returns the name of the parameter type, or NULL if this is not a typed - // or a type-parameterized test. - const char* type_param() const { - if (type_param_.get() != nullptr) return type_param_->c_str(); - return nullptr; - } - - // Returns the text representation of the value parameter, or NULL if this - // is not a value-parameterized test. - const char* value_param() const { - if (value_param_.get() != nullptr) return value_param_->c_str(); - return nullptr; - } - - // Returns the file name where this test is defined. - const char* file() const { return location_.file.c_str(); } - - // Returns the line where this test is defined. - int line() const { return location_.line; } - - // Return true if this test should not be run because it's in another shard. - bool is_in_another_shard() const { return is_in_another_shard_; } - - // Returns true if this test should run, that is if the test is not - // disabled (or it is disabled but the also_run_disabled_tests flag has - // been specified) and its full name matches the user-specified filter. - // - // Google Test allows the user to filter the tests by their full names. - // The full name of a test Bar in test suite Foo is defined as - // "Foo.Bar". Only the tests that match the filter will run. - // - // A filter is a colon-separated list of glob (not regex) patterns, - // optionally followed by a '-' and a colon-separated list of - // negative patterns (tests to exclude). A test is run if it - // matches one of the positive patterns and does not match any of - // the negative patterns. - // - // For example, *A*:Foo.* is a filter that matches any string that - // contains the character 'A' or starts with "Foo.". - bool should_run() const { return should_run_; } - - // Returns true iff this test will appear in the XML report. - bool is_reportable() const { - // The XML report includes tests matching the filter, excluding those - // run in other shards. - return matches_filter_ && !is_in_another_shard_; - } - - // Returns the result of the test. - const TestResult* result() const { return &result_; } - - private: -#if GTEST_HAS_DEATH_TEST - friend class internal::DefaultDeathTestFactory; -#endif // GTEST_HAS_DEATH_TEST - friend class Test; - friend class TestSuite; - friend class internal::UnitTestImpl; - friend class internal::StreamingListenerTest; - friend TestInfo* internal::MakeAndRegisterTestInfo( - const char* test_suite_name, const char* name, const char* type_param, - const char* value_param, internal::CodeLocation code_location, - internal::TypeId fixture_class_id, internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc, - internal::TestFactoryBase* factory); - - // Constructs a TestInfo object. The newly constructed instance assumes - // ownership of the factory object. - TestInfo(const std::string& test_suite_name, const std::string& name, - const char* a_type_param, // NULL if not a type-parameterized test - const char* a_value_param, // NULL if not a value-parameterized test - internal::CodeLocation a_code_location, - internal::TypeId fixture_class_id, - internal::TestFactoryBase* factory); - - // Increments the number of death tests encountered in this test so - // far. - int increment_death_test_count() { - return result_.increment_death_test_count(); - } - - // Creates the test object, runs it, records its result, and then - // deletes it. - void Run(); - - static void ClearTestResult(TestInfo* test_info) { - test_info->result_.Clear(); - } - - // These fields are immutable properties of the test. - const std::string test_suite_name_; // test suite name - const std::string name_; // Test name - // Name of the parameter type, or NULL if this is not a typed or a - // type-parameterized test. - const std::unique_ptr type_param_; - // Text representation of the value parameter, or NULL if this is not a - // value-parameterized test. - const std::unique_ptr value_param_; - internal::CodeLocation location_; - const internal::TypeId fixture_class_id_; // ID of the test fixture class - bool should_run_; // True iff this test should run - bool is_disabled_; // True iff this test is disabled - bool matches_filter_; // True if this test matches the - // user-specified filter. - bool is_in_another_shard_; // Will be run in another shard. - internal::TestFactoryBase* const factory_; // The factory that creates - // the test object - - // This field is mutable and needs to be reset before running the - // test for the second time. - TestResult result_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); -}; - -// A test suite, which consists of a vector of TestInfos. -// -// TestSuite is not copyable. -class GTEST_API_ TestSuite { - public: - // Creates a TestSuite with the given name. - // - // TestSuite does NOT have a default constructor. Always use this - // constructor to create a TestSuite object. - // - // Arguments: - // - // name: name of the test suite - // a_type_param: the name of the test's type parameter, or NULL if - // this is not a type-parameterized test. - // set_up_tc: pointer to the function that sets up the test suite - // tear_down_tc: pointer to the function that tears down the test suite - TestSuite(const char* name, const char* a_type_param, - internal::SetUpTestSuiteFunc set_up_tc, - internal::TearDownTestSuiteFunc tear_down_tc); - - // Destructor of TestSuite. - virtual ~TestSuite(); - - // Gets the name of the TestSuite. - const char* name() const { return name_.c_str(); } - - // Returns the name of the parameter type, or NULL if this is not a - // type-parameterized test suite. - const char* type_param() const { - if (type_param_.get() != nullptr) return type_param_->c_str(); - return nullptr; - } - - // Returns true if any test in this test suite should run. - bool should_run() const { return should_run_; } - - // Gets the number of successful tests in this test suite. - int successful_test_count() const; - - // Gets the number of skipped tests in this test suite. - int skipped_test_count() const; - - // Gets the number of failed tests in this test suite. - int failed_test_count() const; - - // Gets the number of disabled tests that will be reported in the XML report. - int reportable_disabled_test_count() const; - - // Gets the number of disabled tests in this test suite. - int disabled_test_count() const; - - // Gets the number of tests to be printed in the XML report. - int reportable_test_count() const; - - // Get the number of tests in this test suite that should run. - int test_to_run_count() const; - - // Gets the number of all tests in this test suite. - int total_test_count() const; - - // Returns true iff the test suite passed. - bool Passed() const { return !Failed(); } - - // Returns true iff the test suite failed. - bool Failed() const { return failed_test_count() > 0; } - - // Returns the elapsed time, in milliseconds. - TimeInMillis elapsed_time() const { return elapsed_time_; } - - // Returns the i-th test among all the tests. i can range from 0 to - // total_test_count() - 1. If i is not in that range, returns NULL. - const TestInfo* GetTestInfo(int i) const; - - // Returns the TestResult that holds test properties recorded during - // execution of SetUpTestSuite and TearDownTestSuite. - const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; } - - private: - friend class Test; - friend class internal::UnitTestImpl; - - // Gets the (mutable) vector of TestInfos in this TestSuite. - std::vector& test_info_list() { return test_info_list_; } - - // Gets the (immutable) vector of TestInfos in this TestSuite. - const std::vector& test_info_list() const { - return test_info_list_; - } - - // Returns the i-th test among all the tests. i can range from 0 to - // total_test_count() - 1. If i is not in that range, returns NULL. - TestInfo* GetMutableTestInfo(int i); - - // Sets the should_run member. - void set_should_run(bool should) { should_run_ = should; } - - // Adds a TestInfo to this test suite. Will delete the TestInfo upon - // destruction of the TestSuite object. - void AddTestInfo(TestInfo * test_info); - - // Clears the results of all tests in this test suite. - void ClearResult(); - - // Clears the results of all tests in the given test suite. - static void ClearTestSuiteResult(TestSuite* test_suite) { - test_suite->ClearResult(); - } - - // Runs every test in this TestSuite. - void Run(); - - // Runs SetUpTestSuite() for this TestSuite. This wrapper is needed - // for catching exceptions thrown from SetUpTestSuite(). - void RunSetUpTestSuite() { - if (set_up_tc_ != nullptr) { - (*set_up_tc_)(); - } - } - - // Runs TearDownTestSuite() for this TestSuite. This wrapper is - // needed for catching exceptions thrown from TearDownTestSuite(). - void RunTearDownTestSuite() { - if (tear_down_tc_ != nullptr) { - (*tear_down_tc_)(); - } - } - - // Returns true iff test passed. - static bool TestPassed(const TestInfo* test_info) { - return test_info->should_run() && test_info->result()->Passed(); - } - - // Returns true iff test skipped. - static bool TestSkipped(const TestInfo* test_info) { - return test_info->should_run() && test_info->result()->Skipped(); - } - - // Returns true iff test failed. - static bool TestFailed(const TestInfo* test_info) { - return test_info->should_run() && test_info->result()->Failed(); - } - - // Returns true iff the test is disabled and will be reported in the XML - // report. - static bool TestReportableDisabled(const TestInfo* test_info) { - return test_info->is_reportable() && test_info->is_disabled_; - } - - // Returns true iff test is disabled. - static bool TestDisabled(const TestInfo* test_info) { - return test_info->is_disabled_; - } - - // Returns true iff this test will appear in the XML report. - static bool TestReportable(const TestInfo* test_info) { - return test_info->is_reportable(); - } - - // Returns true if the given test should run. - static bool ShouldRunTest(const TestInfo* test_info) { - return test_info->should_run(); - } - - // Shuffles the tests in this test suite. - void ShuffleTests(internal::Random* random); - - // Restores the test order to before the first shuffle. - void UnshuffleTests(); - - // Name of the test suite. - std::string name_; - // Name of the parameter type, or NULL if this is not a typed or a - // type-parameterized test. - const std::unique_ptr type_param_; - // The vector of TestInfos in their original order. It owns the - // elements in the vector. - std::vector test_info_list_; - // Provides a level of indirection for the test list to allow easy - // shuffling and restoring the test order. The i-th element in this - // vector is the index of the i-th test in the shuffled test list. - std::vector test_indices_; - // Pointer to the function that sets up the test suite. - internal::SetUpTestSuiteFunc set_up_tc_; - // Pointer to the function that tears down the test suite. - internal::TearDownTestSuiteFunc tear_down_tc_; - // True iff any test in this test suite should run. - bool should_run_; - // Elapsed time, in milliseconds. - TimeInMillis elapsed_time_; - // Holds test properties recorded during execution of SetUpTestSuite and - // TearDownTestSuite. - TestResult ad_hoc_test_result_; - - // We disallow copying TestSuites. - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestSuite); -}; - -// An Environment object is capable of setting up and tearing down an -// environment. You should subclass this to define your own -// environment(s). -// -// An Environment object does the set-up and tear-down in virtual -// methods SetUp() and TearDown() instead of the constructor and the -// destructor, as: -// -// 1. You cannot safely throw from a destructor. This is a problem -// as in some cases Google Test is used where exceptions are enabled, and -// we may want to implement ASSERT_* using exceptions where they are -// available. -// 2. You cannot use ASSERT_* directly in a constructor or -// destructor. -class Environment { - public: - // The d'tor is virtual as we need to subclass Environment. - virtual ~Environment() {} - - // Override this to define how to set up the environment. - virtual void SetUp() {} - - // Override this to define how to tear down the environment. - virtual void TearDown() {} - private: - // If you see an error about overriding the following function or - // about it being private, you have mis-spelled SetUp() as Setup(). - struct Setup_should_be_spelled_SetUp {}; - virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; } -}; - -#if GTEST_HAS_EXCEPTIONS - -// Exception which can be thrown from TestEventListener::OnTestPartResult. -class GTEST_API_ AssertionException - : public internal::GoogleTestFailureException { - public: - explicit AssertionException(const TestPartResult& result) - : GoogleTestFailureException(result) {} -}; - -#endif // GTEST_HAS_EXCEPTIONS - -// The interface for tracing execution of tests. The methods are organized in -// the order the corresponding events are fired. -class TestEventListener { - public: - virtual ~TestEventListener() {} - - // Fired before any test activity starts. - virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; - - // Fired before each iteration of tests starts. There may be more than - // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration - // index, starting from 0. - virtual void OnTestIterationStart(const UnitTest& unit_test, - int iteration) = 0; - - // Fired before environment set-up for each iteration of tests starts. - virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; - - // Fired after environment set-up for each iteration of tests ends. - virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; - - // Fired before the test suite starts. - virtual void OnTestSuiteStart(const TestSuite& /*test_suite*/) {} - - // Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Fired before the test starts. - virtual void OnTestStart(const TestInfo& test_info) = 0; - - // Fired after a failed assertion or a SUCCEED() invocation. - // If you want to throw an exception from this function to skip to the next - // TEST, it must be AssertionException defined above, or inherited from it. - virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; - - // Fired after the test ends. - virtual void OnTestEnd(const TestInfo& test_info) = 0; - - // Fired after the test suite ends. - virtual void OnTestSuiteEnd(const TestSuite& /*test_suite*/) {} - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Fired before environment tear-down for each iteration of tests starts. - virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; - - // Fired after environment tear-down for each iteration of tests ends. - virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; - - // Fired after each iteration of tests finishes. - virtual void OnTestIterationEnd(const UnitTest& unit_test, - int iteration) = 0; - - // Fired after all test activities have ended. - virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; -}; - -// The convenience class for users who need to override just one or two -// methods and are not concerned that a possible change to a signature of -// the methods they override will not be caught during the build. For -// comments about each method please see the definition of TestEventListener -// above. -class EmptyTestEventListener : public TestEventListener { - public: - void OnTestProgramStart(const UnitTest& /*unit_test*/) override {} - void OnTestIterationStart(const UnitTest& /*unit_test*/, - int /*iteration*/) override {} - void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {} - void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {} - void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {} -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - void OnTestCaseStart(const TestCase& /*test_case*/) override {} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - void OnTestStart(const TestInfo& /*test_info*/) override {} - void OnTestPartResult(const TestPartResult& /*test_part_result*/) override {} - void OnTestEnd(const TestInfo& /*test_info*/) override {} - void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {} -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - void OnTestCaseEnd(const TestCase& /*test_case*/) override {} -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {} - void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {} - void OnTestIterationEnd(const UnitTest& /*unit_test*/, - int /*iteration*/) override {} - void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {} -}; - -// TestEventListeners lets users add listeners to track events in Google Test. -class GTEST_API_ TestEventListeners { - public: - TestEventListeners(); - ~TestEventListeners(); - - // Appends an event listener to the end of the list. Google Test assumes - // the ownership of the listener (i.e. it will delete the listener when - // the test program finishes). - void Append(TestEventListener* listener); - - // Removes the given event listener from the list and returns it. It then - // becomes the caller's responsibility to delete the listener. Returns - // NULL if the listener is not found in the list. - TestEventListener* Release(TestEventListener* listener); - - // Returns the standard listener responsible for the default console - // output. Can be removed from the listeners list to shut down default - // console output. Note that removing this object from the listener list - // with Release transfers its ownership to the caller and makes this - // function return NULL the next time. - TestEventListener* default_result_printer() const { - return default_result_printer_; - } - - // Returns the standard listener responsible for the default XML output - // controlled by the --gtest_output=xml flag. Can be removed from the - // listeners list by users who want to shut down the default XML output - // controlled by this flag and substitute it with custom one. Note that - // removing this object from the listener list with Release transfers its - // ownership to the caller and makes this function return NULL the next - // time. - TestEventListener* default_xml_generator() const { - return default_xml_generator_; - } - - private: - friend class TestSuite; - friend class TestInfo; - friend class internal::DefaultGlobalTestPartResultReporter; - friend class internal::NoExecDeathTest; - friend class internal::TestEventListenersAccessor; - friend class internal::UnitTestImpl; - - // Returns repeater that broadcasts the TestEventListener events to all - // subscribers. - TestEventListener* repeater(); - - // Sets the default_result_printer attribute to the provided listener. - // The listener is also added to the listener list and previous - // default_result_printer is removed from it and deleted. The listener can - // also be NULL in which case it will not be added to the list. Does - // nothing if the previous and the current listener objects are the same. - void SetDefaultResultPrinter(TestEventListener* listener); - - // Sets the default_xml_generator attribute to the provided listener. The - // listener is also added to the listener list and previous - // default_xml_generator is removed from it and deleted. The listener can - // also be NULL in which case it will not be added to the list. Does - // nothing if the previous and the current listener objects are the same. - void SetDefaultXmlGenerator(TestEventListener* listener); - - // Controls whether events will be forwarded by the repeater to the - // listeners in the list. - bool EventForwardingEnabled() const; - void SuppressEventForwarding(); - - // The actual list of listeners. - internal::TestEventRepeater* repeater_; - // Listener responsible for the standard result output. - TestEventListener* default_result_printer_; - // Listener responsible for the creation of the XML output file. - TestEventListener* default_xml_generator_; - - // We disallow copying TestEventListeners. - GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners); -}; - -// A UnitTest consists of a vector of TestSuites. -// -// This is a singleton class. The only instance of UnitTest is -// created when UnitTest::GetInstance() is first called. This -// instance is never deleted. -// -// UnitTest is not copyable. -// -// This class is thread-safe as long as the methods are called -// according to their specification. -class GTEST_API_ UnitTest { - public: - // Gets the singleton UnitTest object. The first time this method - // is called, a UnitTest object is constructed and returned. - // Consecutive calls will return the same object. - static UnitTest* GetInstance(); - - // Runs all tests in this UnitTest object and prints the result. - // Returns 0 if successful, or 1 otherwise. - // - // This method can only be called from the main thread. - // - // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - int Run() GTEST_MUST_USE_RESULT_; - - // Returns the working directory when the first TEST() or TEST_F() - // was executed. The UnitTest object owns the string. - const char* original_working_dir() const; - - // Returns the TestSuite object for the test that's currently running, - // or NULL if no test is running. - const TestSuite* current_test_suite() const GTEST_LOCK_EXCLUDED_(mutex_); - -// Legacy API is still available but deprecated -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - const TestCase* current_test_case() const GTEST_LOCK_EXCLUDED_(mutex_); -#endif - - // Returns the TestInfo object for the test that's currently running, - // or NULL if no test is running. - const TestInfo* current_test_info() const - GTEST_LOCK_EXCLUDED_(mutex_); - - // Returns the random seed used at the start of the current test run. - int random_seed() const; - - // Returns the ParameterizedTestSuiteRegistry object used to keep track of - // value-parameterized tests and instantiate and register them. - // - // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() - GTEST_LOCK_EXCLUDED_(mutex_); - - // Gets the number of successful test suites. - int successful_test_suite_count() const; - - // Gets the number of failed test suites. - int failed_test_suite_count() const; - - // Gets the number of all test suites. - int total_test_suite_count() const; - - // Gets the number of all test suites that contain at least one test - // that should run. - int test_suite_to_run_count() const; - - // Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - int successful_test_case_count() const; - int failed_test_case_count() const; - int total_test_case_count() const; - int test_case_to_run_count() const; -#endif // EMOVE_LEGACY_TEST_CASEAPI - - // Gets the number of successful tests. - int successful_test_count() const; - - // Gets the number of skipped tests. - int skipped_test_count() const; - - // Gets the number of failed tests. - int failed_test_count() const; - - // Gets the number of disabled tests that will be reported in the XML report. - int reportable_disabled_test_count() const; - - // Gets the number of disabled tests. - int disabled_test_count() const; - - // Gets the number of tests to be printed in the XML report. - int reportable_test_count() const; - - // Gets the number of all tests. - int total_test_count() const; - - // Gets the number of tests that should run. - int test_to_run_count() const; - - // Gets the time of the test program start, in ms from the start of the - // UNIX epoch. - TimeInMillis start_timestamp() const; - - // Gets the elapsed time, in milliseconds. - TimeInMillis elapsed_time() const; - - // Returns true iff the unit test passed (i.e. all test suites passed). - bool Passed() const; - - // Returns true iff the unit test failed (i.e. some test suite failed - // or something outside of all tests failed). - bool Failed() const; - - // Gets the i-th test suite among all the test suites. i can range from 0 to - // total_test_suite_count() - 1. If i is not in that range, returns NULL. - const TestSuite* GetTestSuite(int i) const; - -// Legacy API is deprecated but still available -#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - const TestCase* GetTestCase(int i) const; -#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_ - - // Returns the TestResult containing information on test failures and - // properties logged outside of individual test suites. - const TestResult& ad_hoc_test_result() const; - - // Returns the list of event listeners that can be used to track events - // inside Google Test. - TestEventListeners& listeners(); - - private: - // Registers and returns a global test environment. When a test - // program is run, all global test environments will be set-up in - // the order they were registered. After all tests in the program - // have finished, all global test environments will be torn-down in - // the *reverse* order they were registered. - // - // The UnitTest object takes ownership of the given environment. - // - // This method can only be called from the main thread. - Environment* AddEnvironment(Environment* env); - - // Adds a TestPartResult to the current TestResult object. All - // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) - // eventually call this to report their results. The user code - // should use the assertion macros instead of calling this directly. - void AddTestPartResult(TestPartResult::Type result_type, - const char* file_name, - int line_number, - const std::string& message, - const std::string& os_stack_trace) - GTEST_LOCK_EXCLUDED_(mutex_); - - // Adds a TestProperty to the current TestResult object when invoked from - // inside a test, to current TestSuite's ad_hoc_test_result_ when invoked - // from SetUpTestSuite or TearDownTestSuite, or to the global property set - // when invoked elsewhere. If the result already contains a property with - // the same key, the value will be updated. - void RecordProperty(const std::string& key, const std::string& value); - - // Gets the i-th test suite among all the test suites. i can range from 0 to - // total_test_suite_count() - 1. If i is not in that range, returns NULL. - TestSuite* GetMutableTestSuite(int i); - - // Accessors for the implementation object. - internal::UnitTestImpl* impl() { return impl_; } - const internal::UnitTestImpl* impl() const { return impl_; } - - // These classes and functions are friends as they need to access private - // members of UnitTest. - friend class ScopedTrace; - friend class Test; - friend class internal::AssertHelper; - friend class internal::StreamingListenerTest; - friend class internal::UnitTestRecordPropertyTestHelper; - friend Environment* AddGlobalTestEnvironment(Environment* env); - friend internal::UnitTestImpl* internal::GetUnitTestImpl(); - friend void internal::ReportFailureInUnknownLocation( - TestPartResult::Type result_type, - const std::string& message); - - // Creates an empty UnitTest. - UnitTest(); - - // D'tor - virtual ~UnitTest(); - - // Pushes a trace defined by SCOPED_TRACE() on to the per-thread - // Google Test trace stack. - void PushGTestTrace(const internal::TraceInfo& trace) - GTEST_LOCK_EXCLUDED_(mutex_); - - // Pops a trace from the per-thread Google Test trace stack. - void PopGTestTrace() - GTEST_LOCK_EXCLUDED_(mutex_); - - // Protects mutable state in *impl_. This is mutable as some const - // methods need to lock it too. - mutable internal::Mutex mutex_; - - // Opaque implementation object. This field is never changed once - // the object is constructed. We don't mark it as const here, as - // doing so will cause a warning in the constructor of UnitTest. - // Mutable state in *impl_ is protected by mutex_. - internal::UnitTestImpl* impl_; - - // We disallow copying UnitTest. - GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); -}; - -// A convenient wrapper for adding an environment for the test -// program. -// -// You should call this before RUN_ALL_TESTS() is called, probably in -// main(). If you use gtest_main, you need to call this before main() -// starts for it to take effect. For example, you can define a global -// variable like this: -// -// testing::Environment* const foo_env = -// testing::AddGlobalTestEnvironment(new FooEnvironment); -// -// However, we strongly recommend you to write your own main() and -// call AddGlobalTestEnvironment() there, as relying on initialization -// of global variables makes the code harder to read and may cause -// problems when you register multiple environments from different -// translation units and the environments have dependencies among them -// (remember that the compiler doesn't guarantee the order in which -// global variables from different translation units are initialized). -inline Environment* AddGlobalTestEnvironment(Environment* env) { - return UnitTest::GetInstance()->AddEnvironment(env); -} - -// Initializes Google Test. This must be called before calling -// RUN_ALL_TESTS(). In particular, it parses a command line for the -// flags that Google Test recognizes. Whenever a Google Test flag is -// seen, it is removed from argv, and *argc is decremented. -// -// No value is returned. Instead, the Google Test flag variables are -// updated. -// -// Calling the function for the second time has no user-visible effect. -GTEST_API_ void InitGoogleTest(int* argc, char** argv); - -// This overloaded version can be used in Windows programs compiled in -// UNICODE mode. -GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); - -// This overloaded version can be used on Arduino/embedded platforms where -// there is no argc/argv. -GTEST_API_ void InitGoogleTest(); - -namespace internal { - -// Separate the error generating code from the code path to reduce the stack -// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers -// when calling EXPECT_* in a tight loop. -template -AssertionResult CmpHelperEQFailure(const char* lhs_expression, - const char* rhs_expression, - const T1& lhs, const T2& rhs) { - return EqFailure(lhs_expression, - rhs_expression, - FormatForComparisonFailureMessage(lhs, rhs), - FormatForComparisonFailureMessage(rhs, lhs), - false); -} - -// This block of code defines operator==/!= -// to block lexical scope lookup. -// It prevents using invalid operator==/!= defined at namespace scope. -struct faketype {}; -inline bool operator==(faketype, faketype) { return true; } -inline bool operator!=(faketype, faketype) { return false; } - -// The helper function for {ASSERT|EXPECT}_EQ. -template -AssertionResult CmpHelperEQ(const char* lhs_expression, - const char* rhs_expression, - const T1& lhs, - const T2& rhs) { - if (lhs == rhs) { - return AssertionSuccess(); - } - - return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs); -} - -// With this overloaded version, we allow anonymous enums to be used -// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums -// can be implicitly cast to BiggestInt. -GTEST_API_ AssertionResult CmpHelperEQ(const char* lhs_expression, - const char* rhs_expression, - BiggestInt lhs, - BiggestInt rhs); - -class EqHelper { - public: - // This templatized version is for the general case. - template < - typename T1, typename T2, - // Disable this overload for cases where one argument is a pointer - // and the other is the null pointer constant. - typename std::enable_if::value || - !std::is_pointer::value>::type* = nullptr> - static AssertionResult Compare(const char* lhs_expression, - const char* rhs_expression, const T1& lhs, - const T2& rhs) { - return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); - } - - // With this overloaded version, we allow anonymous enums to be used - // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous - // enums can be implicitly cast to BiggestInt. - // - // Even though its body looks the same as the above version, we - // cannot merge the two, as it will make anonymous enums unhappy. - static AssertionResult Compare(const char* lhs_expression, - const char* rhs_expression, - BiggestInt lhs, - BiggestInt rhs) { - return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs); - } - - template - static AssertionResult Compare( - const char* lhs_expression, const char* rhs_expression, - // Handle cases where '0' is used as a null pointer literal. - std::nullptr_t /* lhs */, T* rhs) { - // We already know that 'lhs' is a null pointer. - return CmpHelperEQ(lhs_expression, rhs_expression, static_cast(nullptr), - rhs); - } -}; - -// Separate the error generating code from the code path to reduce the stack -// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers -// when calling EXPECT_OP in a tight loop. -template -AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2, - const T1& val1, const T2& val2, - const char* op) { - return AssertionFailure() - << "Expected: (" << expr1 << ") " << op << " (" << expr2 - << "), actual: " << FormatForComparisonFailureMessage(val1, val2) - << " vs " << FormatForComparisonFailureMessage(val2, val1); -} - -// A macro for implementing the helper functions needed to implement -// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste -// of similar code. -// -// For each templatized helper function, we also define an overloaded -// version for BiggestInt in order to reduce code bloat and allow -// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled -// with gcc 4. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - -#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ -template \ -AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ - const T1& val1, const T2& val2) {\ - if (val1 op val2) {\ - return AssertionSuccess();\ - } else {\ - return CmpHelperOpFailure(expr1, expr2, val1, val2, #op);\ - }\ -}\ -GTEST_API_ AssertionResult CmpHelper##op_name(\ - const char* expr1, const char* expr2, BiggestInt val1, BiggestInt val2) - -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. - -// Implements the helper function for {ASSERT|EXPECT}_NE -GTEST_IMPL_CMP_HELPER_(NE, !=); -// Implements the helper function for {ASSERT|EXPECT}_LE -GTEST_IMPL_CMP_HELPER_(LE, <=); -// Implements the helper function for {ASSERT|EXPECT}_LT -GTEST_IMPL_CMP_HELPER_(LT, <); -// Implements the helper function for {ASSERT|EXPECT}_GE -GTEST_IMPL_CMP_HELPER_(GE, >=); -// Implements the helper function for {ASSERT|EXPECT}_GT -GTEST_IMPL_CMP_HELPER_(GT, >); - -#undef GTEST_IMPL_CMP_HELPER_ - -// The helper function for {ASSERT|EXPECT}_STREQ. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2); - -// The helper function for {ASSERT|EXPECT}_STRCASEEQ. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2); - -// The helper function for {ASSERT|EXPECT}_STRNE. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2); - -// The helper function for {ASSERT|EXPECT}_STRCASENE. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, - const char* s2_expression, - const char* s1, - const char* s2); - - -// Helper function for *_STREQ on wide strings. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression, - const char* s2_expression, - const wchar_t* s1, - const wchar_t* s2); - -// Helper function for *_STRNE on wide strings. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, - const char* s2_expression, - const wchar_t* s1, - const wchar_t* s2); - -} // namespace internal - -// IsSubstring() and IsNotSubstring() are intended to be used as the -// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by -// themselves. They check whether needle is a substring of haystack -// (NULL is considered a substring of itself only), and return an -// appropriate error message when they fail. -// -// The {needle,haystack}_expr arguments are the stringified -// expressions that generated the two real arguments. -GTEST_API_ AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const char* needle, const char* haystack); -GTEST_API_ AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const wchar_t* needle, const wchar_t* haystack); -GTEST_API_ AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const char* needle, const char* haystack); -GTEST_API_ AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const wchar_t* needle, const wchar_t* haystack); -GTEST_API_ AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::string& needle, const ::std::string& haystack); -GTEST_API_ AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::string& needle, const ::std::string& haystack); - -#if GTEST_HAS_STD_WSTRING -GTEST_API_ AssertionResult IsSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::wstring& needle, const ::std::wstring& haystack); -GTEST_API_ AssertionResult IsNotSubstring( - const char* needle_expr, const char* haystack_expr, - const ::std::wstring& needle, const ::std::wstring& haystack); -#endif // GTEST_HAS_STD_WSTRING - -namespace internal { - -// Helper template function for comparing floating-points. -// -// Template parameter: -// -// RawType: the raw floating-point type (either float or double) -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -template -AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression, - const char* rhs_expression, - RawType lhs_value, - RawType rhs_value) { - const FloatingPoint lhs(lhs_value), rhs(rhs_value); - - if (lhs.AlmostEquals(rhs)) { - return AssertionSuccess(); - } - - ::std::stringstream lhs_ss; - lhs_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << lhs_value; - - ::std::stringstream rhs_ss; - rhs_ss << std::setprecision(std::numeric_limits::digits10 + 2) - << rhs_value; - - return EqFailure(lhs_expression, - rhs_expression, - StringStreamToString(&lhs_ss), - StringStreamToString(&rhs_ss), - false); -} - -// Helper function for implementing ASSERT_NEAR. -// -// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. -GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, - const char* expr2, - const char* abs_error_expr, - double val1, - double val2, - double abs_error); - -// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. -// A class that enables one to stream messages to assertion macros -class GTEST_API_ AssertHelper { - public: - // Constructor. - AssertHelper(TestPartResult::Type type, - const char* file, - int line, - const char* message); - ~AssertHelper(); - - // Message assignment is a semantic trick to enable assertion - // streaming; see the GTEST_MESSAGE_ macro below. - void operator=(const Message& message) const; - - private: - // We put our data in a struct so that the size of the AssertHelper class can - // be as small as possible. This is important because gcc is incapable of - // re-using stack space even for temporary variables, so every EXPECT_EQ - // reserves stack space for another AssertHelper. - struct AssertHelperData { - AssertHelperData(TestPartResult::Type t, - const char* srcfile, - int line_num, - const char* msg) - : type(t), file(srcfile), line(line_num), message(msg) { } - - TestPartResult::Type const type; - const char* const file; - int const line; - std::string const message; - - private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData); - }; - - AssertHelperData* const data_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); -}; - -enum GTestColor { COLOR_DEFAULT, COLOR_RED, COLOR_GREEN, COLOR_YELLOW }; - -GTEST_API_ GTEST_ATTRIBUTE_PRINTF_(2, 3) void ColoredPrintf(GTestColor color, - const char* fmt, - ...); - -} // namespace internal - -// The pure interface class that all value-parameterized tests inherit from. -// A value-parameterized class must inherit from both ::testing::Test and -// ::testing::WithParamInterface. In most cases that just means inheriting -// from ::testing::TestWithParam, but more complicated test hierarchies -// may need to inherit from Test and WithParamInterface at different levels. -// -// This interface has support for accessing the test parameter value via -// the GetParam() method. -// -// Use it with one of the parameter generator defining functions, like Range(), -// Values(), ValuesIn(), Bool(), and Combine(). -// -// class FooTest : public ::testing::TestWithParam { -// protected: -// FooTest() { -// // Can use GetParam() here. -// } -// ~FooTest() override { -// // Can use GetParam() here. -// } -// void SetUp() override { -// // Can use GetParam() here. -// } -// void TearDown override { -// // Can use GetParam() here. -// } -// }; -// TEST_P(FooTest, DoesBar) { -// // Can use GetParam() method here. -// Foo foo; -// ASSERT_TRUE(foo.DoesBar(GetParam())); -// } -// INSTANTIATE_TEST_SUITE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); - -template -class WithParamInterface { - public: - typedef T ParamType; - virtual ~WithParamInterface() {} - - // The current parameter value. Is also available in the test fixture's - // constructor. - static const ParamType& GetParam() { - GTEST_CHECK_(parameter_ != nullptr) - << "GetParam() can only be called inside a value-parameterized test " - << "-- did you intend to write TEST_P instead of TEST_F?"; - return *parameter_; - } - - private: - // Sets parameter value. The caller is responsible for making sure the value - // remains alive and unchanged throughout the current test. - static void SetParam(const ParamType* parameter) { - parameter_ = parameter; - } - - // Static value used for accessing parameter during a test lifetime. - static const ParamType* parameter_; - - // TestClass must be a subclass of WithParamInterface and Test. - template friend class internal::ParameterizedTestFactory; -}; - -template -const T* WithParamInterface::parameter_ = nullptr; - -// Most value-parameterized classes can ignore the existence of -// WithParamInterface, and can just inherit from ::testing::TestWithParam. - -template -class TestWithParam : public Test, public WithParamInterface { -}; - -// Macros for indicating success/failure in test code. - -// Skips test in runtime. -// Skipping test aborts current function. -// Skipped tests are neither successful nor failed. -#define GTEST_SKIP() GTEST_SKIP_("Skipped") - -// ADD_FAILURE unconditionally adds a failure to the current test. -// SUCCEED generates a success - it doesn't automatically make the -// current test successful, as a test is only successful when it has -// no failure. -// -// EXPECT_* verifies that a certain condition is satisfied. If not, -// it behaves like ADD_FAILURE. In particular: -// -// EXPECT_TRUE verifies that a Boolean condition is true. -// EXPECT_FALSE verifies that a Boolean condition is false. -// -// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except -// that they will also abort the current function on failure. People -// usually want the fail-fast behavior of FAIL and ASSERT_*, but those -// writing data-driven tests often find themselves using ADD_FAILURE -// and EXPECT_* more. - -// Generates a nonfatal failure with a generic message. -#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") - -// Generates a nonfatal failure at the given source file location with -// a generic message. -#define ADD_FAILURE_AT(file, line) \ - GTEST_MESSAGE_AT_(file, line, "Failed", \ - ::testing::TestPartResult::kNonFatalFailure) - -// Generates a fatal failure with a generic message. -#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") - -// Like GTEST_FAIL(), but at the given source file location. -#define GTEST_FAIL_AT(file, line) \ - GTEST_MESSAGE_AT_(file, line, "Failed", \ - ::testing::TestPartResult::kFatalFailure) - -// Define this macro to 1 to omit the definition of FAIL(), which is a -// generic name and clashes with some other libraries. -#if !GTEST_DONT_DEFINE_FAIL -# define FAIL() GTEST_FAIL() -#endif - -// Generates a success with a generic message. -#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") - -// Define this macro to 1 to omit the definition of SUCCEED(), which -// is a generic name and clashes with some other libraries. -#if !GTEST_DONT_DEFINE_SUCCEED -# define SUCCEED() GTEST_SUCCEED() -#endif - -// Macros for testing exceptions. -// -// * {ASSERT|EXPECT}_THROW(statement, expected_exception): -// Tests that the statement throws the expected exception. -// * {ASSERT|EXPECT}_NO_THROW(statement): -// Tests that the statement doesn't throw any exception. -// * {ASSERT|EXPECT}_ANY_THROW(statement): -// Tests that the statement throws an exception. - -#define EXPECT_THROW(statement, expected_exception) \ - GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) -#define EXPECT_NO_THROW(statement) \ - GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) -#define EXPECT_ANY_THROW(statement) \ - GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) -#define ASSERT_THROW(statement, expected_exception) \ - GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) -#define ASSERT_NO_THROW(statement) \ - GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) -#define ASSERT_ANY_THROW(statement) \ - GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) - -// Boolean assertions. Condition can be either a Boolean expression or an -// AssertionResult. For more information on how to use AssertionResult with -// these macros see comments on that class. -#define EXPECT_TRUE(condition) \ - GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ - GTEST_NONFATAL_FAILURE_) -#define EXPECT_FALSE(condition) \ - GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ - GTEST_NONFATAL_FAILURE_) -#define ASSERT_TRUE(condition) \ - GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ - GTEST_FATAL_FAILURE_) -#define ASSERT_FALSE(condition) \ - GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ - GTEST_FATAL_FAILURE_) - -// Macros for testing equalities and inequalities. -// -// * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2 -// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 -// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 -// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 -// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 -// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 -// -// When they are not, Google Test prints both the tested expressions and -// their actual values. The values must be compatible built-in types, -// or you will get a compiler error. By "compatible" we mean that the -// values can be compared by the respective operator. -// -// Note: -// -// 1. It is possible to make a user-defined type work with -// {ASSERT|EXPECT}_??(), but that requires overloading the -// comparison operators and is thus discouraged by the Google C++ -// Usage Guide. Therefore, you are advised to use the -// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are -// equal. -// -// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on -// pointers (in particular, C strings). Therefore, if you use it -// with two C strings, you are testing how their locations in memory -// are related, not how their content is related. To compare two C -// strings by content, use {ASSERT|EXPECT}_STR*(). -// -// 3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to -// {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you -// what the actual value is when it fails, and similarly for the -// other comparisons. -// -// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() -// evaluate their arguments, which is undefined. -// -// 5. These macros evaluate their arguments exactly once. -// -// Examples: -// -// EXPECT_NE(Foo(), 5); -// EXPECT_EQ(a_pointer, NULL); -// ASSERT_LT(i, array_size); -// ASSERT_GT(records.size(), 0) << "There is no record left."; - -#define EXPECT_EQ(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) -#define EXPECT_NE(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) -#define EXPECT_LE(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) -#define EXPECT_LT(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) -#define EXPECT_GE(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) -#define EXPECT_GT(val1, val2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) - -#define GTEST_ASSERT_EQ(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2) -#define GTEST_ASSERT_NE(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) -#define GTEST_ASSERT_LE(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) -#define GTEST_ASSERT_LT(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) -#define GTEST_ASSERT_GE(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) -#define GTEST_ASSERT_GT(val1, val2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) - -// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of -// ASSERT_XY(), which clashes with some users' own code. - -#if !GTEST_DONT_DEFINE_ASSERT_EQ -# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) -#endif - -#if !GTEST_DONT_DEFINE_ASSERT_NE -# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) -#endif - -#if !GTEST_DONT_DEFINE_ASSERT_LE -# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) -#endif - -#if !GTEST_DONT_DEFINE_ASSERT_LT -# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) -#endif - -#if !GTEST_DONT_DEFINE_ASSERT_GE -# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) -#endif - -#if !GTEST_DONT_DEFINE_ASSERT_GT -# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) -#endif - -// C-string Comparisons. All tests treat NULL and any non-NULL string -// as different. Two NULLs are equal. -// -// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 -// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 -// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case -// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case -// -// For wide or narrow string objects, you can use the -// {ASSERT|EXPECT}_??() macros. -// -// Don't depend on the order in which the arguments are evaluated, -// which is undefined. -// -// These macros evaluate their arguments exactly once. - -#define EXPECT_STREQ(s1, s2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) -#define EXPECT_STRNE(s1, s2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) -#define EXPECT_STRCASEEQ(s1, s2) \ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) -#define EXPECT_STRCASENE(s1, s2)\ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) - -#define ASSERT_STREQ(s1, s2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2) -#define ASSERT_STRNE(s1, s2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) -#define ASSERT_STRCASEEQ(s1, s2) \ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2) -#define ASSERT_STRCASENE(s1, s2)\ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) - -// Macros for comparing floating-point numbers. -// -// * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2): -// Tests that two float values are almost equal. -// * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2): -// Tests that two double values are almost equal. -// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): -// Tests that v1 and v2 are within the given distance to each other. -// -// Google Test uses ULP-based comparison to automatically pick a default -// error bound that is appropriate for the operands. See the -// FloatingPoint template class in gtest-internal.h if you are -// interested in the implementation details. - -#define EXPECT_FLOAT_EQ(val1, val2)\ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - val1, val2) - -#define EXPECT_DOUBLE_EQ(val1, val2)\ - EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - val1, val2) - -#define ASSERT_FLOAT_EQ(val1, val2)\ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - val1, val2) - -#define ASSERT_DOUBLE_EQ(val1, val2)\ - ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ - val1, val2) - -#define EXPECT_NEAR(val1, val2, abs_error)\ - EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ - val1, val2, abs_error) - -#define ASSERT_NEAR(val1, val2, abs_error)\ - ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ - val1, val2, abs_error) - -// These predicate format functions work on floating-point values, and -// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. -// -// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); - -// Asserts that val1 is less than, or almost equal to, val2. Fails -// otherwise. In particular, it fails if either val1 or val2 is NaN. -GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, - float val1, float val2); -GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, - double val1, double val2); - - -#if GTEST_OS_WINDOWS - -// Macros that test for HRESULT failure and success, these are only useful -// on Windows, and rely on Windows SDK macros and APIs to compile. -// -// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) -// -// When expr unexpectedly fails or succeeds, Google Test prints the -// expected result and the actual result with both a human-readable -// string representation of the error, if available, as well as the -// hex result code. -# define EXPECT_HRESULT_SUCCEEDED(expr) \ - EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) - -# define ASSERT_HRESULT_SUCCEEDED(expr) \ - ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) - -# define EXPECT_HRESULT_FAILED(expr) \ - EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) - -# define ASSERT_HRESULT_FAILED(expr) \ - ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) - -#endif // GTEST_OS_WINDOWS - -// Macros that execute statement and check that it doesn't generate new fatal -// failures in the current thread. -// -// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); -// -// Examples: -// -// EXPECT_NO_FATAL_FAILURE(Process()); -// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; -// -#define ASSERT_NO_FATAL_FAILURE(statement) \ - GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) -#define EXPECT_NO_FATAL_FAILURE(statement) \ - GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) - -// Causes a trace (including the given source file path and line number, -// and the given message) to be included in every test failure message generated -// by code in the scope of the lifetime of an instance of this class. The effect -// is undone with the destruction of the instance. -// -// The message argument can be anything streamable to std::ostream. -// -// Example: -// testing::ScopedTrace trace("file.cc", 123, "message"); -// -class GTEST_API_ ScopedTrace { - public: - // The c'tor pushes the given source file location and message onto - // a trace stack maintained by Google Test. - - // Template version. Uses Message() to convert the values into strings. - // Slow, but flexible. - template - ScopedTrace(const char* file, int line, const T& message) { - PushTrace(file, line, (Message() << message).GetString()); - } - - // Optimize for some known types. - ScopedTrace(const char* file, int line, const char* message) { - PushTrace(file, line, message ? message : "(null)"); - } - - ScopedTrace(const char* file, int line, const std::string& message) { - PushTrace(file, line, message); - } - - // The d'tor pops the info pushed by the c'tor. - // - // Note that the d'tor is not virtual in order to be efficient. - // Don't inherit from ScopedTrace! - ~ScopedTrace(); - - private: - void PushTrace(const char* file, int line, std::string message); - - GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); -} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its - // c'tor and d'tor. Therefore it doesn't - // need to be used otherwise. - -// Causes a trace (including the source file path, the current line -// number, and the given message) to be included in every test failure -// message generated by code in the current scope. The effect is -// undone when the control leaves the current scope. -// -// The message argument can be anything streamable to std::ostream. -// -// In the implementation, we include the current line number as part -// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s -// to appear in the same block - as long as they are on different -// lines. -// -// Assuming that each thread maintains its own stack of traces. -// Therefore, a SCOPED_TRACE() would (correctly) only affect the -// assertions in its own thread. -#define SCOPED_TRACE(message) \ - ::testing::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ - __FILE__, __LINE__, (message)) - - -// Compile-time assertion for type equality. -// StaticAssertTypeEq() compiles iff type1 and type2 are -// the same type. The value it returns is not interesting. -// -// Instead of making StaticAssertTypeEq a class template, we make it a -// function template that invokes a helper class template. This -// prevents a user from misusing StaticAssertTypeEq by -// defining objects of that type. -// -// CAVEAT: -// -// When used inside a method of a class template, -// StaticAssertTypeEq() is effective ONLY IF the method is -// instantiated. For example, given: -// -// template class Foo { -// public: -// void Bar() { testing::StaticAssertTypeEq(); } -// }; -// -// the code: -// -// void Test1() { Foo foo; } -// -// will NOT generate a compiler error, as Foo::Bar() is never -// actually instantiated. Instead, you need: -// -// void Test2() { Foo foo; foo.Bar(); } -// -// to cause a compiler error. -template -bool StaticAssertTypeEq() { - (void)internal::StaticAssertTypeEqHelper(); - return true; -} - -// Defines a test. -// -// The first parameter is the name of the test suite, and the second -// parameter is the name of the test within the test suite. -// -// The convention is to end the test suite name with "Test". For -// example, a test suite for the Foo class can be named FooTest. -// -// Test code should appear between braces after an invocation of -// this macro. Example: -// -// TEST(FooTest, InitializesCorrectly) { -// Foo foo; -// EXPECT_TRUE(foo.StatusIsOK()); -// } - -// Note that we call GetTestTypeId() instead of GetTypeId< -// ::testing::Test>() here to get the type ID of testing::Test. This -// is to work around a suspected linker bug when using Google Test as -// a framework on Mac OS X. The bug causes GetTypeId< -// ::testing::Test>() to return different values depending on whether -// the call is from the Google Test framework itself or from user test -// code. GetTestTypeId() is guaranteed to always return the same -// value, as it always calls GetTypeId<>() from the Google Test -// framework. -#define GTEST_TEST(test_suite_name, test_name) \ - GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \ - ::testing::internal::GetTestTypeId()) - -// Define this macro to 1 to omit the definition of TEST(), which -// is a generic name and clashes with some other libraries. -#if !GTEST_DONT_DEFINE_TEST -#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name) -#endif - -// Defines a test that uses a test fixture. -// -// The first parameter is the name of the test fixture class, which -// also doubles as the test suite name. The second parameter is the -// name of the test within the test suite. -// -// A test fixture class must be declared earlier. The user should put -// the test code between braces after using this macro. Example: -// -// class FooTest : public testing::Test { -// protected: -// void SetUp() override { b_.AddElement(3); } -// -// Foo a_; -// Foo b_; -// }; -// -// TEST_F(FooTest, InitializesCorrectly) { -// EXPECT_TRUE(a_.StatusIsOK()); -// } -// -// TEST_F(FooTest, ReturnsElementCountCorrectly) { -// EXPECT_EQ(a_.size(), 0); -// EXPECT_EQ(b_.size(), 1); -// } -// -// GOOGLETEST_CM0011 DO NOT DELETE -#define TEST_F(test_fixture, test_name)\ - GTEST_TEST_(test_fixture, test_name, test_fixture, \ - ::testing::internal::GetTypeId()) - -// Returns a path to temporary directory. -// Tries to determine an appropriate directory for the platform. -GTEST_API_ std::string TempDir(); - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -// Dynamically registers a test with the framework. -// -// This is an advanced API only to be used when the `TEST` macros are -// insufficient. The macros should be preferred when possible, as they avoid -// most of the complexity of calling this function. -// -// The `factory` argument is a factory callable (move-constructible) object or -// function pointer that creates a new instance of the Test object. It -// handles ownership to the caller. The signature of the callable is -// `Fixture*()`, where `Fixture` is the test fixture class for the test. All -// tests registered with the same `test_suite_name` must return the same -// fixture type. This is checked at runtime. -// -// The framework will infer the fixture class from the factory and will call -// the `SetUpTestSuite` and `TearDownTestSuite` for it. -// -// Must be called before `RUN_ALL_TESTS()` is invoked, otherwise behavior is -// undefined. -// -// Use case example: -// -// class MyFixture : public ::testing::Test { -// public: -// // All of these optional, just like in regular macro usage. -// static void SetUpTestSuite() { ... } -// static void TearDownTestSuite() { ... } -// void SetUp() override { ... } -// void TearDown() override { ... } -// }; -// -// class MyTest : public MyFixture { -// public: -// explicit MyTest(int data) : data_(data) {} -// void TestBody() override { ... } -// -// private: -// int data_; -// }; -// -// void RegisterMyTests(const std::vector& values) { -// for (int v : values) { -// ::testing::RegisterTest( -// "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr, -// std::to_string(v).c_str(), -// __FILE__, __LINE__, -// // Important to use the fixture type as the return type here. -// [=]() -> MyFixture* { return new MyTest(v); }); -// } -// } -// ... -// int main(int argc, char** argv) { -// std::vector values_to_test = LoadValuesFromConfig(); -// RegisterMyTests(values_to_test); -// ... -// return RUN_ALL_TESTS(); -// } -// -template -TestInfo* RegisterTest(const char* test_suite_name, const char* test_name, - const char* type_param, const char* value_param, - const char* file, int line, Factory factory) { - using TestT = typename std::remove_pointer::type; - - class FactoryImpl : public internal::TestFactoryBase { - public: - explicit FactoryImpl(Factory f) : factory_(std::move(f)) {} - Test* CreateTest() override { return factory_(); } - - private: - Factory factory_; - }; - - return internal::MakeAndRegisterTestInfo( - test_suite_name, test_name, type_param, value_param, - internal::CodeLocation(file, line), internal::GetTypeId(), - internal::SuiteApiResolver::GetSetUpCaseOrSuite(file, line), - internal::SuiteApiResolver::GetTearDownCaseOrSuite(file, line), - new FactoryImpl{std::move(factory)}); -} - -} // namespace testing - -// Use this function in main() to run all tests. It returns 0 if all -// tests are successful, or 1 otherwise. -// -// RUN_ALL_TESTS() should be invoked after the command line has been -// parsed by InitGoogleTest(). -// -// This function was formerly a macro; thus, it is in the global -// namespace and has an all-caps name. -int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_; - -inline int RUN_ALL_TESTS() { - return ::testing::UnitTest::GetInstance()->Run(); -} - -GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 - -#endif // GTEST_INCLUDE_GTEST_GTEST_H_ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/main.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/main.cpp deleted file mode 100644 index d7700e8bc..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/main.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include "gtest/gtest.h" - -std::function()> get_console_logger_factory() -{ - return [] { - return Aws::MakeShared( - "console_logger", Aws::Utils::Logging::LogLevel::Warn); - }; -} - -std::string aws_prefix; - -void parse_args(int argc, char** argv) -{ - const std::string resource_prefix_option = "--aws_prefix="; - for (int i = 1; i < argc; i++) { - std::string arg = argv[i]; - if (arg.find(resource_prefix_option) == 0) { - aws_prefix = arg.substr(resource_prefix_option.length()); // get whatever value after the '=' - break; - } - } -} - -int main(int argc, char** argv) -{ - parse_args(argc, argv); - Aws::SDKOptions options; - options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Warn; - options.loggingOptions.logger_create_fn = get_console_logger_factory(); - Aws::InitAPI(options); - ::testing::InitGoogleTest(&argc, argv); - int exit_code = RUN_ALL_TESTS(); - Aws::ShutdownAPI(options); - return exit_code; -} diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/CMakeLists.txt b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/CMakeLists.txt deleted file mode 100644 index af43b1d3b..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -project(lambda-test-fun LANGUAGES CXX) - -# resources are the actual lambda functions deployed and exercised by the tests -add_executable(${PROJECT_NAME} lambda_function.cpp) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_link_libraries(${PROJECT_NAME} PRIVATE aws-lambda-runtime) -# package the lambda function into lambda.zip -add_custom_target(aws-lambda-package-lambda-test-fun - COMMAND "${CMAKE_SOURCE_DIR}/packaging/packager" "${CMAKE_CURRENT_BINARY_DIR}/lambda-test-fun" - DEPENDS ${PROJECT_NAME}) - -add_custom_target(aws-lambda-package-lambda-test-fun-no-glibc - COMMAND "${CMAKE_SOURCE_DIR}/packaging/packager" -d "${CMAKE_CURRENT_BINARY_DIR}/lambda-test-fun" - DEPENDS ${PROJECT_NAME}) diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/lambda_function.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/lambda_function.cpp deleted file mode 100644 index 43f3dac21..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/resources/lambda_function.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include -#include - -#include -#include -#include - -using namespace aws::lambda_runtime; - -constexpr unsigned int AWSLOGO_PNG_LEN = 1451; -extern unsigned char awslogo_png[AWSLOGO_PNG_LEN]; // NOLINT - -invocation_response echo_success(invocation_request const& request) -{ - return invocation_response::success(request.payload, "application/json"); -} - -invocation_response echo_failure(invocation_request const& /*request*/) -{ - return invocation_response::failure("Test error message", "TestErrorType"); -} - -invocation_response binary_response(invocation_request const& /*request*/) -{ - const std::string png((char*)awslogo_png, AWSLOGO_PNG_LEN); - return invocation_response::success(png, "image/png"); -} - -int main(int argc, char* argv[]) -{ - std::unordered_map> handlers; - handlers.emplace("echo_success", echo_success); - handlers.emplace("echo_failure", echo_failure); - handlers.emplace("binary_response", binary_response); - - if (argc < 2) { - aws::logging::log_error("lambda_fun", "Missing handler argument. Exiting."); - return -1; - } - - auto it = handlers.find(argv[1]); - if (it == handlers.end()) { - aws::logging::log_error("lambda_fun", "Handler %s not found. Exiting.", argv[1]); - return -2; - } - run_handler(it->second); - return 0; -} - -/* clang-format off */ -unsigned char awslogo_png[] = { // NOLINT - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, // NOLINT - 0x18, 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x35, 0xf8, 0xdc, 0x7e, 0x00, 0x00, 0x00, 0x04, 0x67, // NOLINT - 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, // NOLINT - 0x00, 0x00, 0x7a, 0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80, 0xe8, 0x00, 0x00, 0x75, // NOLINT - 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, // NOLINT - 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, // NOLINT - 0x00, 0x00, 0x01, 0x59, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, // NOLINT - 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, // NOLINT - 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, // NOLINT - 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, // NOLINT - 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, // NOLINT - 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, // NOLINT - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, // NOLINT - 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, // NOLINT - 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, // NOLINT - 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, // NOLINT - 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // NOLINT - 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, // NOLINT - 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, // NOLINT - 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, // NOLINT - 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, // NOLINT - 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, // NOLINT - 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, // NOLINT - 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, // NOLINT - 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x4c, 0xc2, 0x27, 0x59, 0x00, 0x00, 0x03, 0xbc, // NOLINT - 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x65, 0x54, 0x6f, 0x6c, 0x53, 0x55, 0x14, 0x3f, 0xe7, 0xbe, 0xd7, 0x75, 0xf2, // NOLINT - 0x18, 0x1a, 0x24, 0xc4, 0xc1, 0x20, 0x0a, 0x6b, 0x27, 0x1b, 0xa3, 0x85, 0xa4, 0x92, 0x29, 0x5d, 0xbb, 0x25, 0x82, // NOLINT - 0xc6, 0x48, 0x0c, 0x61, 0x43, 0x0d, 0x4a, 0x98, 0x91, 0x19, 0xa3, 0x7e, 0x22, 0x41, 0x09, 0xc9, 0xe0, 0x83, 0x06, // NOLINT - 0x35, 0x31, 0x46, 0x8c, 0xc1, 0x05, 0x25, 0xc1, 0xc8, 0x97, 0xc5, 0xc4, 0x38, 0xd0, 0x84, 0x60, 0x52, 0xba, 0x2d, // NOLINT - 0x68, 0xd0, 0x6c, 0x65, 0x56, 0xb1, 0xab, 0x3a, 0x70, 0x09, 0x68, 0x84, 0x0f, 0xba, 0xd5, 0xba, 0xbe, 0x77, 0x8f, // NOLINT - 0xbf, 0x7b, 0xcb, 0x32, 0xff, 0x9c, 0xe6, 0xdc, 0x73, 0xcf, 0xb9, 0xbf, 0xf3, 0xf7, 0xde, 0x57, 0x26, 0x50, 0x74, // NOLINT - 0x7d, 0x47, 0x4c, 0x74, 0xf0, 0x2a, 0xb6, 0x2b, 0x88, 0x69, 0x21, 0x6b, 0x1a, 0xd5, 0x25, 0x6f, 0x07, 0x79, 0x33, // NOLINT - 0x29, 0x26, 0xea, 0xad, 0xf1, 0xd5, 0xae, 0x7c, 0x3e, 0x33, 0x1d, 0x8d, 0xa7, 0x1f, 0x21, 0x09, 0x7a, 0x1d, 0x97, // NOLINT - 0x7a, 0xbe, 0xfb, 0x7a, 0xe8, 0x6a, 0x34, 0x9e, 0x4a, 0x88, 0x48, 0x9f, 0x2a, 0x07, 0x8f, 0x05, 0x61, 0xa7, 0x43, // NOLINT - 0x31, 0xbd, 0x6e, 0xe2, 0x09, 0xd1, 0x34, 0x2b, 0xb5, 0xaf, 0x30, 0x9a, 0x39, 0xab, 0xac, 0x41, 0xcb, 0x0a, 0x04, // NOLINT - 0x1a, 0x55, 0x4a, 0xed, 0x12, 0x96, 0x27, 0x85, 0x69, 0x33, 0x7b, 0x33, 0x6f, 0x6a, 0xd2, 0x93, 0x8e, 0x1b, 0xda, // NOLINT - 0x56, 0x76, 0x25, 0x61, 0x71, 0xa2, 0x77, 0x86, 0x6a, 0xbd, 0x07, 0xfc, 0x0a, 0xdd, 0x5b, 0x0d, 0xa4, 0x77, 0x43, // NOLINT - 0xc6, 0xb4, 0xe7, 0x34, 0x30, 0xd3, 0xc7, 0x9a, 0xf8, 0xa8, 0x62, 0xb5, 0x85, 0x99, 0xdf, 0xd5, 0x5a, 0xdb, 0xd8, // NOLINT - 0x8a, 0xba, 0xba, 0x9c, 0x89, 0x5c, 0xe6, 0x54, 0x21, 0x97, 0x7d, 0xa9, 0xfe, 0x56, 0x1a, 0x2b, 0x8e, 0x0e, 0x9d, // NOLINT - 0x43, 0x09, 0x6f, 0xa0, 0x8a, 0xce, 0x1f, 0x73, 0xc3, 0x85, 0xc0, 0xaf, 0x14, 0x1d, 0xd2, 0x69, 0x13, 0x10, 0xb4, // NOLINT - 0x74, 0xb6, 0x5c, 0xba, 0x40, 0xc4, 0x49, 0xab, 0x09, 0xa7, 0x44, 0xe8, 0x98, 0x2f, 0x7e, 0xc9, 0x71, 0x5c, 0x56, // NOLINT - 0x22, 0x37, 0x2e, 0x8d, 0x65, 0x26, 0x0b, 0x63, 0xe7, 0xfa, 0x8b, 0xb9, 0xec, 0x19, 0x13, 0x1b, 0x85, 0x63, 0x44, // NOLINT - 0xad, 0x9b, 0x56, 0x91, 0x52, 0xc7, 0x10, 0xb4, 0x01, 0xea, 0x15, 0xc8, 0x16, 0x1c, 0x5c, 0x9d, 0xc8, 0x65, 0x37, // NOLINT - 0x44, 0xe2, 0xc9, 0x77, 0x88, 0x54, 0x84, 0x39, 0xd8, 0x2b, 0x5a, 0x1d, 0x11, 0x92, 0x7e, 0x26, 0xde, 0xa3, 0xc5, // NOLINT - 0xdf, 0xae, 0xd8, 0x2d, 0x90, 0xa6, 0xad, 0x13, 0xe3, 0xd9, 0x6c, 0x34, 0x96, 0x3c, 0x24, 0xc4, 0x07, 0xe0, 0xff, // NOLINT - 0x3b, 0xf8, 0x74, 0xc5, 0x57, 0x7b, 0x27, 0xf3, 0x99, 0x6b, 0xb6, 0x0d, 0xad, 0xd4, 0x09, 0x4d, 0xb4, 0x44, 0x09, // NOLINT - 0x6d, 0x91, 0x19, 0xef, 0x21, 0x62, 0x3e, 0x05, 0x50, 0x18, 0x4c, 0x1c, 0xa8, 0x41, 0x12, 0x69, 0xd0, 0xe2, 0xec, // NOLINT - 0x40, 0xe0, 0xcb, 0x61, 0xdf, 0xff, 0x14, 0x23, 0x58, 0xc0, 0xec, 0xf6, 0xe0, 0xf8, 0x67, 0xf9, 0xd3, 0xfb, 0xd2, // NOLINT - 0xe0, 0x0a, 0xb9, 0xa1, 0x3e, 0xdc, 0xd5, 0x72, 0x8c, 0xf7, 0x39, 0xa8, 0xf7, 0x87, 0x42, 0xf2, 0x81, 0xb1, 0xdb, // NOLINT - 0x04, 0xa8, 0x76, 0x0d, 0x78, 0xe4, 0xfb, 0x8b, 0xd9, 0x9f, 0x8a, 0xc5, 0xcf, 0xfe, 0x82, 0x71, 0x3d, 0xba, 0x58, // NOLINT - 0x6c, 0x00, 0x21, 0xbd, 0xf4, 0x73, 0x88, 0x32, 0x8b, 0xec, 0x43, 0xf5, 0xc3, 0xf9, 0xfc, 0xf9, 0x1b, 0xb8, 0xd8, // NOLINT - 0xeb, 0xc0, 0xbf, 0x0c, 0x3e, 0x6d, 0xf0, 0xd1, 0x58, 0xe7, 0xf2, 0xa6, 0x75, 0xed, 0x77, 0xe1, 0x21, 0x5c, 0x2b, // NOLINT - 0x8e, 0x65, 0x3f, 0x0c, 0x84, 0x4e, 0x6a, 0x2d, 0xeb, 0x8c, 0xbf, 0x6b, 0x16, 0x12, 0x3e, 0x8c, 0x54, 0xaf, 0x45, // NOLINT - 0x62, 0xed, 0x1b, 0xf0, 0x8a, 0xca, 0x98, 0xeb, 0x38, 0x9c, 0x17, 0x47, 0x5b, 0xd3, 0x77, 0xe7, 0xc7, 0x07, 0x2e, // NOLINT - 0xc1, 0x9e, 0x55, 0xca, 0x89, 0xfb, 0x81, 0x8c, 0x54, 0xf1, 0x74, 0xc1, 0x09, 0x85, 0x36, 0x57, 0x82, 0xca, 0x27, // NOLINT - 0x55, 0xf7, 0xa0, 0x19, 0x05, 0x1d, 0x8f, 0xc4, 0x92, 0x53, 0x0b, 0x1c, 0xf6, 0x6a, 0x58, 0x56, 0xdf, 0x1e, 0xa6, // NOLINT - 0x9e, 0x1f, 0x2c, 0xf8, 0xe6, 0x92, 0x48, 0xa4, 0x12, 0x2b, 0xd7, 0x26, 0x77, 0x9a, 0x4a, 0x8c, 0xe9, 0xce, 0x96, // NOLINT - 0xf4, 0x1d, 0x10, 0xb6, 0xc3, 0xa6, 0xa6, 0xfb, 0xea, 0x1a, 0xd7, 0x76, 0xae, 0x9e, 0xd3, 0x1b, 0x1b, 0x37, 0x2e, // NOLINT - 0x5a, 0x15, 0x6f, 0x8f, 0x40, 0x47, 0x1d, 0x55, 0x5a, 0x03, 0xbd, 0xa1, 0xb5, 0xfd, 0x71, 0xa2, 0x8e, 0x6d, 0x44, // NOLINT - 0x4f, 0x78, 0x73, 0x76, 0x46, 0x66, 0xbc, 0x30, 0xf3, 0x74, 0xe7, 0xe9, 0xe0, 0x41, 0x52, 0x60, 0x5c, 0x8b, 0x25, // NOLINT - 0x13, 0xe4, 0x9f, 0xe7, 0xff, 0xd5, 0x6f, 0xc2, 0xaa, 0x42, 0xbe, 0xa2, 0x7a, 0x1a, 0xa7, 0xb6, 0xa0, 0x1c, 0xf2, // NOLINT - 0x1c, 0xae, 0x7c, 0xc1, 0x72, 0x94, 0x9e, 0xc5, 0xd1, 0x34, 0x3f, 0x43, 0x27, 0xe6, 0x90, 0x82, 0x04, 0xdf, 0x52, // NOLINT - 0x8b, 0xdb, 0x4c, 0x79, 0x93, 0xc4, 0x26, 0xea, 0xce, 0x77, 0xf1, 0xc0, 0xc0, 0x40, 0x60, 0x30, 0x18, 0x21, 0x1f, // NOLINT - 0xea, 0x48, 0x3b, 0x7d, 0xe9, 0x8c, 0xa6, 0x66, 0xdb, 0x85, 0xc3, 0xdd, 0x34, 0x5b, 0x7a, 0xbb, 0xae, 0x2d, 0xe4, // NOLINT - 0x96, 0xb7, 0x2b, 0x0a, 0x1c, 0xc5, 0xfa, 0x61, 0x40, 0x67, 0x58, 0xde, 0xa2, 0x45, 0x78, 0x2f, 0xaf, 0x40, 0xd9, // NOLINT - 0x08, 0x36, 0x37, 0x7f, 0x92, 0x7b, 0xe9, 0x37, 0xc8, 0xff, 0x91, 0x49, 0x6c, 0x8d, 0x7d, 0xb8, 0x35, 0xfe, 0x57, // NOLINT - 0x57, 0xd6, 0x8c, 0x58, 0x61, 0xaa, 0xa5, 0x6e, 0x94, 0x74, 0x19, 0x86, 0x85, 0xe0, 0x17, 0x4d, 0xbb, 0x96, 0xa4, // NOLINT - 0x9f, 0xb6, 0xc2, 0xc5, 0x7c, 0xea, 0x66, 0xd6, 0xc3, 0xe0, 0x41, 0xd4, 0x76, 0x1e, 0xcf, 0x60, 0x82, 0xae, 0xd0, // NOLINT - 0x75, 0x9e, 0x1f, 0x99, 0xc5, 0x9b, 0x45, 0xde, 0xa3, 0x3a, 0x0a, 0x68, 0x25, 0x02, 0xde, 0x03, 0xec, 0x83, 0x30, // NOLINT - 0xb5, 0x21, 0xc6, 0x14, 0xe4, 0x53, 0x28, 0xe5, 0x79, 0xec, 0xbf, 0x61, 0x5b, 0x15, 0xda, 0x44, 0x8b, 0xd5, 0xf6, // NOLINT - 0xfb, 0xe9, 0x51, 0x1c, 0xbc, 0x80, 0xc0, 0x6d, 0xf6, 0x4b, 0x28, 0x99, 0x48, 0xf4, 0x0b, 0xd6, 0x5f, 0xc1, 0x7f, // NOLINT - 0x80, 0xcd, 0xc8, 0x6e, 0x01, 0x2f, 0x01, 0xd7, 0x53, 0x0d, 0x7e, 0xb3, 0xd6, 0x36, 0x84, 0x24, 0x47, 0x78, 0x0f, // NOLINT - 0x7d, 0x84, 0x98, 0x2e, 0x2d, 0xa3, 0xfd, 0x74, 0x1b, 0x1d, 0x9e, 0xef, 0x00, 0x46, 0x54, 0xe9, 0xc3, 0xc9, 0x92, // NOLINT - 0xbc, 0x8f, 0x3f, 0xbe, 0x59, 0x4a, 0xc1, 0x69, 0x13, 0x0c, 0x2d, 0x48, 0xb2, 0x0c, 0x7b, 0xd3, 0xb6, 0xb9, 0xf0, // NOLINT - 0x69, 0xf0, 0x14, 0x76, 0x17, 0x21, 0x47, 0xc8, 0xa1, 0x0c, 0x3f, 0x6d, 0x8b, 0x80, 0x0a, 0xc0, 0x71, 0xaa, 0xe5, // NOLINT - 0xdd, 0x54, 0x36, 0xfb, 0xbf, 0x01, 0x02, 0x9d, 0x70, 0x74, 0xcd, 0x2a, 0x03, 0x15, 0x00, 0x00, 0x00, 0x00, 0x49, // NOLINT - 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; // NOLINT -/* clang-format on */ diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/runtime_tests.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/runtime_tests.cpp deleted file mode 100644 index 003242969..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/runtime_tests.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "gtest/gtest.h" - -extern std::string aws_prefix; - -namespace { - -using namespace Aws::Lambda; - -constexpr auto S3BUCKET = "aws-lambda-cpp-tests"; -constexpr auto S3KEY = "lambda-test-fun.zip"; -constexpr auto REQUEST_TIMEOUT = 15 * 1000; - -struct LambdaRuntimeTest : public ::testing::Test { - LambdaClient m_lambda_client; - Aws::IAM::IAMClient m_iam_client; - static Aws::Client::ClientConfiguration create_iam_config() - { - Aws::Client::ClientConfiguration config; - config.requestTimeoutMs = REQUEST_TIMEOUT; - config.region = Aws::Region::US_EAST_1; - return config; - } - - static Aws::Client::ClientConfiguration create_lambda_config() - { - Aws::Client::ClientConfiguration config; - config.requestTimeoutMs = REQUEST_TIMEOUT; - config.region = Aws::Environment::GetEnv("AWS_REGION"); - return config; - } - - static Aws::String build_resource_name(Aws::String const& name) - { - return aws_prefix.c_str() + name; // NOLINT - } - - LambdaRuntimeTest() : m_lambda_client(create_lambda_config()), m_iam_client(create_iam_config()) {} - - ~LambdaRuntimeTest() override - { - // clean up in case we exited one test abnormally - delete_function(build_resource_name("echo_success"), false /*assert*/); - delete_function(build_resource_name("echo_failure"), false /*assert*/); - delete_function(build_resource_name("binary_response"), false /*assert*/); - } - - Aws::String get_role_arn(Aws::String const& role_name) - { - using namespace Aws::IAM; - using namespace Aws::IAM::Model; - GetRoleRequest request; - request.WithRoleName(role_name); - auto outcome = m_iam_client.GetRole(request); - EXPECT_TRUE(outcome.IsSuccess()); - if (outcome.IsSuccess()) { - return outcome.GetResult().GetRole().GetArn(); - } - return {}; - } - - void create_function(Aws::String const& function_name, Aws::String const& handler_name) - { - Model::CreateFunctionRequest create_function_request; - create_function_request.SetHandler(handler_name); - create_function_request.SetFunctionName(function_name); - // I ran into eventual-consistency issues when creating the role dynamically as part of the test. - create_function_request.SetRole(get_role_arn("integration-tests")); - Model::FunctionCode funcode; - funcode.WithS3Bucket(S3BUCKET).WithS3Key(build_resource_name(S3KEY)); - create_function_request.SetCode(funcode); - create_function_request.SetRuntime(Aws::Lambda::Model::Runtime::provided); - - auto outcome = m_lambda_client.CreateFunction(create_function_request); - ASSERT_TRUE(outcome.IsSuccess()) << "Failed to create function " << function_name; - } - - void delete_function(Aws::String const& function_name, bool assert = true) - { - Model::DeleteFunctionRequest delete_function_request; - delete_function_request.SetFunctionName(function_name); - auto outcome = m_lambda_client.DeleteFunction(delete_function_request); - if (assert) { - ASSERT_TRUE(outcome.IsSuccess()) << "Failed to delete function " << function_name; - } - } -}; - -TEST_F(LambdaRuntimeTest, echo_success) -{ - Aws::String const funcname = build_resource_name("echo_success"); - constexpr auto payload_content = "Hello, Lambda!"; - create_function(funcname, "echo_success" /*handler_name*/); - Model::InvokeRequest invoke_request; - invoke_request.SetFunctionName(funcname); - invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); - invoke_request.SetContentType("application/json"); - - std::shared_ptr payload = Aws::MakeShared("FunctionTest"); - Aws::Utils::Json::JsonValue json_payload; - json_payload.WithString("barbaz", payload_content); - *payload << json_payload.View().WriteCompact(); - invoke_request.SetBody(payload); - - Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); - EXPECT_TRUE(invoke_outcome.IsSuccess()); - Aws::StringStream output; - if (!invoke_outcome.IsSuccess()) { - delete_function(funcname); - return; - } - EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); - auto const json_response = Aws::Utils::Json::JsonValue(invoke_outcome.GetResult().GetPayload()); - EXPECT_TRUE(json_response.WasParseSuccessful()); - EXPECT_STREQ(payload_content, json_response.View().GetString("barbaz").c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, echo_unicode) -{ - Aws::String const funcname = build_resource_name("echo_success"); // re-use the echo method but with Unicode input - constexpr auto payload_content = "画像は1000語の価値がある"; - create_function(funcname, "echo_success" /*handler_name*/); - Model::InvokeRequest invoke_request; - invoke_request.SetFunctionName(funcname); - invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); - invoke_request.SetContentType("application/json"); - - std::shared_ptr payload = Aws::MakeShared("FunctionTest"); - Aws::Utils::Json::JsonValue json_payload; - json_payload.WithString("UnicodeText", payload_content); - *payload << json_payload.View().WriteCompact(); - invoke_request.SetBody(payload); - - Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); - EXPECT_TRUE(invoke_outcome.IsSuccess()); - Aws::StringStream output; - if (!invoke_outcome.IsSuccess()) { - delete_function(funcname); - return; - } - EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); - auto const json_response = Aws::Utils::Json::JsonValue(invoke_outcome.GetResult().GetPayload()); - EXPECT_TRUE(json_response.WasParseSuccessful()); - EXPECT_STREQ(payload_content, json_response.View().GetString("UnicodeText").c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, echo_failure) -{ - Aws::String const funcname = build_resource_name("echo_failure"); - create_function(funcname, "echo_failure" /*handler_name*/); - Model::InvokeRequest invoke_request; - invoke_request.SetFunctionName(funcname); - invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); - - Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); - EXPECT_TRUE(invoke_outcome.IsSuccess()); - EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); - EXPECT_STREQ("Unhandled", invoke_outcome.GetResult().GetFunctionError().c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, binary_response) -{ - Aws::String const funcname = build_resource_name("binary_response"); - unsigned long constexpr expected_length = 1451; - create_function(funcname, "binary_response" /*handler_name*/); - Model::InvokeRequest invoke_request; - invoke_request.SetFunctionName(funcname); - invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); - - Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); - EXPECT_TRUE(invoke_outcome.IsSuccess()); - EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); - EXPECT_EQ(expected_length, invoke_outcome.GetResult().GetPayload().tellp()); - delete_function(funcname); -} -} // namespace diff --git a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/version_tests.cpp b/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/version_tests.cpp deleted file mode 100644 index 862c68019..000000000 --- a/aws-lambda-java-runtime-interface-client/src/main/jni/deps/aws-lambda-cpp-0.2.7/tests/version_tests.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include "gtest/gtest.h" - -using namespace aws::lambda_runtime; - -TEST(VersionTests, get_version_major) -{ - auto version = get_version_major(); - ASSERT_EQ(0, version); -} - -TEST(VersionTests, get_version_minor) -{ - auto version = get_version_minor(); - ASSERT_GE(version, 1); -} - -TEST(VersionTests, get_version_patch) -{ - auto version = get_version_patch(); - ASSERT_GE(version, 0); -} diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent index 2b1ba299e..b077df89d 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/Dockerfile.agent @@ -1,7 +1,9 @@ +# syntax=docker/dockerfile:1.4 FROM public.ecr.aws/amazoncorretto/amazoncorretto:8 -# Install docker and buildx extension -RUN yum install -y docker tar gzip unzip file findutils +# Install docker and buildx extension. +# gnupg2 provides gpg/gpgv/gpg-agent so build-jni-lib.sh can GPG-verify the prebuilt assets. +RUN yum install -y --allowerasing docker tar gzip unzip file findutils gnupg2 COPY --from=docker/buildx-bin:latest /buildx /usr/libexec/docker/cli-plugins/docker-buildx diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh new file mode 100755 index 000000000..5f03cad59 --- /dev/null +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +set -uo pipefail + +MAX_ATTEMPTS="${RETRY_MAX_ATTEMPTS:-5}" +BASE_DELAY="${RETRY_BASE_DELAY:-5}" +MAX_DELAY="${RETRY_MAX_DELAY:-60}" + +if (( $# == 0 )); then + >&2 echo "usage: docker-retry.sh [args...]" + exit 2 +fi + +attempt=1 +while true; do + "$@" && exit 0 + status=$? + + if (( attempt >= MAX_ATTEMPTS )); then + >&2 echo "docker-retry: '$*' failed after ${attempt} attempt(s) (exit ${status}); giving up." + exit "$status" + fi + + # Exponential backoff: BASE_DELAY * 2^(attempt-1), capped at MAX_DELAY. + backoff=$(( BASE_DELAY * (2 ** (attempt - 1)) )) + (( backoff > MAX_DELAY )) && backoff=$MAX_DELAY + # Full jitter: wait a random duration in [0, backoff] so concurrent jobs + # spread out instead of retrying at the same moment. + delay=$(( RANDOM % (backoff + 1) )) + + >&2 echo "docker-retry: '$*' failed (exit ${status}); attempt ${attempt}/${MAX_ATTEMPTS}, retrying in ${delay}s." + sleep "$delay" + attempt=$(( attempt + 1 )) +done diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_all.sh b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_all.sh index 77bcb5933..cedda98bf 100755 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_all.sh +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_all.sh @@ -5,12 +5,16 @@ set -euo pipefail CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}" DRYRUN="${DRYRUN-0}" +# When set, only the matching platform from each buildspec is run. Used by CI to +# run each architecture on its own native runner instead of emulating via QEMU. +PLATFORM_FILTER="${PLATFORM_FILTER-}" function usage { - echo "usage: test_all.sh buildspec_yml_dir" + echo "usage: test_all.sh buildspec_yml_dir_or_file" echo "Runs all buildspec build-matrix combinations via test_one.sh." echo "Required:" - echo " buildspec_yml_dir Used to specify the CodeBuild buildspec template file." + echo " buildspec_yml_dir_or_file A directory of buildspec templates (runs every *.yml)," + echo " or a single buildspec .yml file." } do_one_yaml() { @@ -21,6 +25,10 @@ do_one_yaml() { RUNTIME_VERSIONS=$(sed '1,/RUNTIME_VERSION/d;/PLATFORM/,$d' "$YML" | sed '/#.*$/d' | tr -d '\-" ') PLATFORMS=$(sed '1,/PLATFORM/d;/phases/,$d' "$YML" | tr -d '\-" ') + if [ -n "$PLATFORM_FILTER" ]; then + PLATFORMS="$PLATFORM_FILTER" + fi + for DISTRO_VERSION in $DISTRO_VERSIONS; do for RUNTIME_VERSION in $RUNTIME_VERSIONS; do for PLATFORM in $PLATFORMS; do @@ -56,9 +64,16 @@ main() { usage exit 1 fi - BUILDSPEC_YML_DIR="$1" + BUILDSPEC_YML_PATH="$1" + + # Allow passing a single buildspec file so CI can parallelize per OS. + if [ -f "$BUILDSPEC_YML_PATH" ]; then + do_one_yaml "$BUILDSPEC_YML_PATH" + return + fi + HAS_YML=0 - for f in "$BUILDSPEC_YML_DIR"/*.yml ; do + for f in "$BUILDSPEC_YML_PATH"/*.yml ; do [ -f "$f" ] || continue; do_one_yaml "$f" HAS_YML=1 diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_one.sh b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_one.sh index ba49e826e..2d20f3162 100755 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_one.sh +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/test_one.sh @@ -60,6 +60,10 @@ main() { ARTIFACTS_DIR="$CODEBUILD_TEMP_DIR/artifacts" mkdir -p "$ARTIFACTS_DIR" + + LOCAL_AGENT_IMAGE="$(get_local_agent_image)" + "$(dirname "$0")"/docker-retry.sh docker pull "$LOCAL_AGENT_IMAGE" || true + # Run CodeBuild local agent. "$(dirname "$0")"/codebuild_build.sh \ -i "$CODEBUILD_IMAGE_TAG" \ @@ -67,7 +71,7 @@ main() { -e "$ENVFILE" \ -b "$BUILDSPEC_YML" \ -s "$(dirname $PWD)" \ - -l "$(get_local_agent_image)" + -l "$LOCAL_AGENT_IMAGE" } main "$@" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml index afc68fd76..65a9da50a 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.alpine.yml @@ -72,7 +72,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --platform="${PLATFORM}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml index e5a586b91..c578235fe 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazoncorretto.yml @@ -68,7 +68,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --platform="${PLATFORM}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml index e3773cf82..0398ca642 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.1.yml @@ -55,7 +55,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index a9836fc6f..816ea6ac4 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -67,7 +67,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --platform="${PLATFORM}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml deleted file mode 100644 index 74d12b01d..000000000 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.centos.yml +++ /dev/null @@ -1,81 +0,0 @@ -version: 0.2 - -env: - variables: - OS_DISTRIBUTION: centos - JAVA_BINARY_LOCATION: "/usr/bin/java" - DOCKER_CLI_EXPERIMENTAL: "enabled" - DOCKER_CLI_PLUGIN_DIR: "/root/.docker/cli-plugins" -batch: - build-matrix: - static: - ignore-failure: false - env: - privileged-mode: true - dynamic: - env: - variables: - DISTRO_VERSION: - - "7" - RUNTIME_VERSION: - - "corretto11" - PLATFORM: - - "linux/amd64" - - "linux/arm64/v8" -phases: - install: - commands: - - > - if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; - then - echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." - else - echo "Performing DockerHub login . . ." - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD - fi - - aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/configure_multi_arch_env.sh - pre_build: - commands: - # Log some environment variables for troubleshooting - - (mvn -v) - # Install events (dependency of serialization) - - (cd aws-lambda-java-events && mvn install) - # Install serialization (dependency of RIC) - - (cd aws-lambda-java-core && mvn install) - - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) - - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - - echo "Extracting and including Runtime Interface Emulator" - - SCRATCH_DIR=".scratch" - - mkdir "${SCRATCH_DIR}" - - > - if [[ "$PLATFORM" == "linux/amd64" ]]; then - RIE="aws-lambda-rie" - elif [[ "$PLATFORM" == "linux/arm64/v8" ]]; then - RIE="aws-lambda-rie-arm64" - else - echo "Platform $PLATFORM is not currently supported." - exit 1 - fi - - tar -xvf aws-lambda-java-runtime-interface-client/test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" - - > - cp "aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.${OS_DISTRIBUTION}" \ - "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - - > - echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ - "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - - echo "Building image ${IMAGE_TAG}" - - > - docker build . \ - -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ - -t "${IMAGE_TAG}" \ - --platform="${PLATFORM}" \ - --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ - --build-arg DISTRO_VERSION="${DISTRO_VERSION}" - build: - commands: - - aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/run_invocation_test.sh - finally: - - aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/fetch_test_container_logs.sh - - aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/clean_up.sh diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml index 222d14a36..d169c7d07 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml @@ -16,8 +16,8 @@ batch: env: variables: DISTRO_VERSION: - - "buster" - - "bullseye" + - "bookworm" + - "trixie" RUNTIME_VERSION: - "corretto11" PLATFORM: @@ -44,7 +44,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" @@ -71,7 +71,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --platform="${PLATFORM}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml index ce153c547..b2ac255f7 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml @@ -16,10 +16,8 @@ batch: env: variables: DISTRO_VERSION: - - "18.04" - - "20.04" - - "21.10" - "22.04" + - "24.04" RUNTIME_VERSION: - "corretto11" PLATFORM: @@ -46,7 +44,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install) + - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" @@ -73,7 +71,7 @@ phases: "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" - echo "Building image ${IMAGE_TAG}" - > - docker build . \ + aws-lambda-java-runtime-interface-client/test/integration/codebuild-local/docker-retry.sh docker build . \ -f "${SCRATCH_DIR}/Dockerfile.function.${OS_DISTRIBUTION}.tmp" \ -t "${IMAGE_TAG}" \ --platform="${PLATFORM}" \ diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/configure_multi_arch_env.sh b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/configure_multi_arch_env.sh index 76153f184..7e1ed3b1e 100755 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/configure_multi_arch_env.sh +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/scripts/configure_multi_arch_env.sh @@ -15,7 +15,7 @@ else fi echo "Installing ${TARGET_EMULATOR} emulator" -docker pull public.ecr.aws/eks-distro-build-tooling/binfmt-misc:qemu-v6.1.0 +"$(dirname "$0")"/../../codebuild-local/docker-retry.sh docker pull public.ecr.aws/eks-distro-build-tooling/binfmt-misc:qemu-v6.1.0 docker run --rm --privileged public.ecr.aws/eks-distro-build-tooling/binfmt-misc:qemu-v6.1.0 --install ${TARGET_EMULATOR} echo "Setting docker build command to default to buildx" echo "Docker buildx version: $(docker buildx version)" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.centos b/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.centos deleted file mode 100644 index 865092393..000000000 --- a/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.centos +++ /dev/null @@ -1,13 +0,0 @@ -ARG DISTRO_VERSION - -FROM public.ecr.aws/docker/library/centos:centos${DISTRO_VERSION} - -RUN rpm --import https://yum.corretto.aws/corretto.key && \ - curl -L -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo && \ - yum install -y java-11-amazon-corretto-devel - -ADD aws-lambda-java-runtime-interface-client/test/integration/test-handler/target/HelloWorld-1.0.jar . - -ENTRYPOINT ["java", "-jar", "./HelloWorld-1.0.jar"] - -CMD ["helloworld.App"] diff --git a/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.debian b/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.debian index 1ef477cc9..019b46a62 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.debian +++ b/aws-lambda-java-runtime-interface-client/test/integration/docker/Dockerfile.function.debian @@ -3,9 +3,9 @@ ARG DISTRO_VERSION FROM public.ecr.aws/debian/debian:${DISTRO_VERSION} as build-image RUN apt-get update && \ - apt-get install -y wget gpg software-properties-common && \ - wget -O- https://apt.corretto.aws/corretto.key | apt-key add - && \ - add-apt-repository 'deb https://apt.corretto.aws stable main' && \ + apt-get install -y wget gnupg ca-certificates && \ + wget -O- https://apt.corretto.aws/corretto.key | gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" > /etc/apt/sources.list.d/corretto.list && \ apt-get update && \ apt-get install -y java-11-amazon-corretto-jdk From 18c3a666e25e9a0adc826f3e21dca7af5ce05a54 Mon Sep 17 00:00:00 2001 From: stefan9283 Date: Thu, 20 Aug 2026 13:26:47 +0000 Subject: [PATCH 172/173] Update Log4J version to fix CVE --- aws-lambda-java-log4j2/README.md | 10 +++++----- aws-lambda-java-log4j2/RELEASE.CHANGELOG.md | 3 +++ aws-lambda-java-log4j2/pom.xml | 2 +- lambda-integration-tests/log4j2-test-function/pom.xml | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/aws-lambda-java-log4j2/README.md b/aws-lambda-java-log4j2/README.md index d8e40ebea..b906ee8f6 100644 --- a/aws-lambda-java-log4j2/README.md +++ b/aws-lambda-java-log4j2/README.md @@ -12,22 +12,22 @@ Example for Maven pom.xml com.amazonaws aws-lambda-java-log4j2 - 1.6.4 + 1.6.5 org.apache.logging.log4j log4j-core - 2.25.4 + 2.25.5 org.apache.logging.log4j log4j-api - 2.25.4 + 2.25.5 org.apache.logging.log4j log4j-layout-template-json - 2.25.4 + 2.25.5 .... @@ -75,7 +75,7 @@ If you are using the [John Rengelman](https://github.com/johnrengelman/shadow) G dependencies{ ... - implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.4' + implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.5' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion } diff --git a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md index 5f43862a3..37e7a8760 100644 --- a/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md +++ b/aws-lambda-java-log4j2/RELEASE.CHANGELOG.md @@ -1,4 +1,7 @@ ### May 19, 2026 +`1.6.5`: +- Updated `log4j-core` and `log4j-api` dependencies to `2.25.5` + `1.6.4`: - Fix regression in `1.6.3` diff --git a/aws-lambda-java-log4j2/pom.xml b/aws-lambda-java-log4j2/pom.xml index 842ef2d48..d136612f7 100644 --- a/aws-lambda-java-log4j2/pom.xml +++ b/aws-lambda-java-log4j2/pom.xml @@ -37,7 +37,7 @@ 1.8 1.8 - 2.25.4 + 2.25.5 5.12.2 diff --git a/lambda-integration-tests/log4j2-test-function/pom.xml b/lambda-integration-tests/log4j2-test-function/pom.xml index b036d1dea..295ddebef 100644 --- a/lambda-integration-tests/log4j2-test-function/pom.xml +++ b/lambda-integration-tests/log4j2-test-function/pom.xml @@ -17,7 +17,7 @@ 21 21 UTF-8 - 2.25.4 + 2.25.5 From c20dc9979f99ed67fd2cf688c8c29315cfcb82ab Mon Sep 17 00:00:00 2001 From: Maxime David Date: Fri, 21 Aug 2026 09:24:38 -0400 Subject: [PATCH 173/173] fix: speed up ci (#637) --- .../test/integration/codebuild/buildspec.os.amazonlinux.2.yml | 2 +- .../test/integration/codebuild/buildspec.os.debian.yml | 2 +- .../test/integration/codebuild/buildspec.os.ubuntu.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml index 816ea6ac4..a229defbc 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.amazonlinux.2.yml @@ -43,7 +43,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml index d169c7d07..02f3915c3 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.debian.yml @@ -44,7 +44,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator" diff --git a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml index b2ac255f7..cb63814d0 100644 --- a/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml +++ b/aws-lambda-java-runtime-interface-client/test/integration/codebuild/buildspec.os.ubuntu.yml @@ -44,7 +44,7 @@ phases: # Install serialization (dependency of RIC) - (cd aws-lambda-java-core && mvn install) - (cd aws-lambda-java-serialization && mvn install) - - (cd aws-lambda-java-runtime-interface-client && mvn install -DargLineForReflectionTestOnly="") + - (cd aws-lambda-java-runtime-interface-client && mvn install -DmultiArch=false -DargLineForReflectionTestOnly="") - (cd aws-lambda-java-runtime-interface-client/test/integration/test-handler && mvn install) - export IMAGE_TAG="java-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" - echo "Extracting and including Runtime Interface Emulator"