-
Notifications
You must be signed in to change notification settings - Fork 1k
test(crac/Autoprime): Add warm-up tests that auto-cover the generated provider of every service module #7188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/master/crac_auto_priming_support
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,9 +26,11 @@ | |
| </parent> | ||
| <artifactId>warmup-tests</artifactId> | ||
| <name>AWS Java SDK :: Test :: Warm-up Tests</name> | ||
| <description>Centralized tests for the CRaC HTTP-client warm-up, run against every sync HTTP client so each client does not | ||
| need its own warm-up test. This is a leaf test module: nothing depends on it, so depending on the HTTP clients here | ||
| does not create a dependency cycle.</description> | ||
| <description>Centralized tests for the CRaC warm-up feature. Covers the SdkWarmUp orchestrator and HTTP-client warm-up | ||
| (run against every HTTP client), plus the generated SdkWarmUpProvider of every service module: the aws-sdk-java | ||
| aggregate dependency puts all services on the test classpath, so new services are tested automatically. This is a | ||
| leaf test module: nothing depends on it, so depending on the HTTP clients and all services here does not create a | ||
| dependency cycle.</description> | ||
| <url>https://aws.amazon.com/sdkforjava</url> | ||
|
|
||
| <dependencyManagement> | ||
|
|
@@ -43,6 +45,14 @@ | |
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <properties> | ||
| <!-- This module loads every service client on the classpath. When JaCoCo measures code coverage it rewrites | ||
| each class to add counters, and this makes the largest generated clients (e.g. SSM, Glue) exceed the | ||
| JVM class-size limit and fail to load (ClassTooLargeException). We do not need coverage numbers for this | ||
| test module, so turn JaCoCo off here. --> | ||
| <jacoco.skip>true</jacoco.skip> | ||
| </properties> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
|
|
@@ -55,6 +65,12 @@ | |
| <version>${awsjavasdk.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>http-auth-aws-crt</artifactId> | ||
| <version>${awsjavasdk.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>apache5-client</artifactId> | ||
|
|
@@ -91,6 +107,31 @@ | |
| <version>${awsjavasdk.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <!-- Every service module, so the generated SdkWarmUpProviders of all services are on the test classpath. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: any reason we want to run tests for all services? How long does it take to run all tests?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we run all services because the warm-up operation is selected per service from each service's model; only executing every generated provider proves each one works. The full module runs offline (Canned Req/Response). These test guard the customer path: a customer with all services on the classpath calls SdkWarmUp.prime(), and a broken provider logs a WARN and skips that service's warm-up. These tests turn that customer-visible WARN into a build failure here. New services are covered automatically, the aggregate dependency picks up each new service, and the completeness test fails the build if a service ships without a registered provider
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will get the exact time once I merge the PR 7183. The issues mentioned in this PR were caught by these tests |
||
| This artifact is updated automatically when a new service is added (FinalizeNewServiceModuleMain), | ||
| so new services are covered by the warm-up tests without any change to this module. --> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>aws-sdk-java</artifactId> | ||
| <version>${awsjavasdk.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>test-utils</artifactId> | ||
| <version>${awsjavasdk.version}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.apache.logging.log4j</groupId> | ||
| <artifactId>log4j-core</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.apache.logging.log4j</groupId> | ||
| <artifactId>log4j-slf4j-impl</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.junit.jupiter</groupId> | ||
| <artifactId>junit-jupiter</artifactId> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| /* | ||
| * Copyright 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. | ||
| */ | ||
|
|
||
| package software.amazon.awssdk.warmup.allservices; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.junit.jupiter.api.Assumptions.assumeTrue; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.ServiceLoader; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.Stream; | ||
| import java.util.stream.StreamSupport; | ||
| import org.apache.logging.log4j.Level; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Named; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
| import software.amazon.awssdk.core.ClientType; | ||
| import software.amazon.awssdk.core.crac.SdkWarmUpProvider; | ||
| import software.amazon.awssdk.testutils.LogCaptor; | ||
|
|
||
| /** | ||
| * Tests the generated {@link SdkWarmUpProvider} of every service module on the classpath. For each provider, warm-up | ||
| * must complete without throwing, must invoke the selected warm-up operation (checked with | ||
| * {@link OperationRecordingInterceptor}), and must not emit SDK warn or error logs. | ||
| * | ||
| * <p>The {@code aws-sdk-java} aggregate artifact puts all service modules on the classpath and is updated when a new | ||
| * service is added, so new services are tested here with no change to this module. | ||
| */ | ||
| class AllServicesWarmUpTest { | ||
|
|
||
| /** | ||
| * Providers for which {@code WarmUpOperationSelector} picked no operation because every operation is streaming, | ||
| * event-streaming, or deprecated. Warm-up only builds and closes the client for these. The test asserts they | ||
| * record no operation, so an entry here fails once codegen starts selecting an operation for the service. | ||
| */ | ||
| private static final Set<String> KNOWN_NO_OP_PROVIDERS = new HashSet<>(Arrays.asList( | ||
| //All APIs are deprecated | ||
| "software.amazon.awssdk.services.cloudhsm.internal.crac.CloudHsmWarmUpProvider", | ||
| "software.amazon.awssdk.services.finspacedata.internal.crac.FinspaceDataWarmUpProvider", | ||
| "software.amazon.awssdk.services.iotthingsgraph.internal.crac.IoTThingsGraphWarmUpProvider", | ||
| "software.amazon.awssdk.services.lexmodelbuilding.internal.crac.LexModelBuildingWarmUpProvider", | ||
| "software.amazon.awssdk.services.proton.internal.crac.ProtonWarmUpProvider", | ||
|
|
||
| // All streaming operations | ||
| "software.amazon.awssdk.services.kinesisvideomedia.internal.crac.KinesisVideoMediaWarmUpProvider", | ||
| "software.amazon.awssdk.services.sagemakerruntimehttp2.internal.crac.SageMakerRuntimeHttp2WarmUpProvider") | ||
| ); | ||
|
|
||
| /** | ||
| * TODO: Remove once cloudfrontkeyvaluestore has a warm-up customization. | ||
| * | ||
| * <p>Its warm-up operation takes an ARN, and the service's endpoint rules reject the generic dummy ARN because | ||
| * they require a CloudFront ARN. Until codegen supplies a valid ARN, skip this provider so the bad ARN does not | ||
| * fail the test. | ||
| */ | ||
| private static final Set<String> CUSTOMIZATION_REQUIRED_PROVIDERS = new HashSet<>(Collections.singletonList( | ||
| "software.amazon.awssdk.services.cloudfrontkeyvaluestore.internal.crac.CloudFrontKeyValueStoreWarmUpProvider") | ||
| ); | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| OperationRecordingInterceptor.reset(); | ||
| } | ||
|
|
||
| /** | ||
| * All generated service providers. Generated providers live in | ||
| * {@code software.amazon.awssdk.services.<service>.internal.crac}, so the filter excludes this module's | ||
| * hand-written test providers in {@code software.amazon.awssdk.http.warmup}. | ||
| */ | ||
| static Stream<Named<SdkWarmUpProvider>> generatedProviders() { | ||
| return StreamSupport.stream(ServiceLoader.load(SdkWarmUpProvider.class).spliterator(), false) | ||
| .filter(p -> p.getClass().getName().startsWith("software.amazon.awssdk.services.")) | ||
| .map(p -> Named.of(p.getClass().getSimpleName(), p)); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = "{0}") | ||
| @MethodSource("generatedProviders") | ||
| void warmUpClient_sync_invokesOperationWithoutErrors(SdkWarmUpProvider provider) { | ||
| verifyWarmUp(provider, ClientType.SYNC, provider.syncClientClassName()); | ||
| } | ||
|
|
||
| @ParameterizedTest(name = "{0}") | ||
| @MethodSource("generatedProviders") | ||
| void warmUpClient_async_invokesOperationWithoutErrors(SdkWarmUpProvider provider) { | ||
| verifyWarmUp(provider, ClientType.ASYNC, provider.asyncClientClassName()); | ||
| } | ||
|
|
||
| private void verifyWarmUp(SdkWarmUpProvider provider, ClientType clientType, String clientClassName) { | ||
| // The client class name is null when the service does not generate this client type, for example an | ||
| // async-only service has no sync client. Skip that case. | ||
| assumeTrue(clientClassName != null, | ||
| () -> provider.getClass().getSimpleName() + " does not generate a " + clientType + " client"); | ||
|
|
||
| assumeTrue(!CUSTOMIZATION_REQUIRED_PROVIDERS.contains(provider.getClass().getName()), | ||
| () -> provider.getClass().getSimpleName() + " is skipped pending a codegen fix"); | ||
|
|
||
| try (LogCaptor logCaptor = LogCaptor.create(Level.WARN)) { | ||
| provider.warmUpClient(clientType); | ||
|
|
||
| // Keep only warnings from SDK loggers and ignore the rest. We assert warm-up emits no SDK warnings, | ||
| // not that the whole classpath is silent. | ||
| List<String> sdkWarnings = logCaptor.loggedEvents().stream() | ||
| .filter(e -> e.getLoggerName().startsWith("software.amazon.awssdk")) | ||
| .map(e -> e.getLoggerName() + " - " | ||
| + e.getMessage().getFormattedMessage()) | ||
| .collect(Collectors.toList()); | ||
| assertThat(sdkWarnings) | ||
| .as("%s warm-up of %s must not emit SDK warn/error logs", clientType, clientClassName) | ||
| .isEmpty(); | ||
| } | ||
|
|
||
| if (KNOWN_NO_OP_PROVIDERS.contains(provider.getClass().getName())) { | ||
| // If this fails, codegen now selects an operation for the service, so remove it from KNOWN_NO_OP_PROVIDERS. | ||
| assertThat(OperationRecordingInterceptor.operationNames()) | ||
| .as("%s is listed in KNOWN_NO_OP_PROVIDERS but recorded an operation; remove the stale entry", | ||
| provider.getClass().getSimpleName()) | ||
| .isEmpty(); | ||
| } else { | ||
| assertThat(OperationRecordingInterceptor.operationNames()) | ||
| .as("%s warm-up of %s must invoke its selected warm-up operation", clientType, clientClassName) | ||
| .isNotEmpty(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * Copyright 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. | ||
| */ | ||
|
|
||
| package software.amazon.awssdk.warmup.allservices; | ||
|
|
||
| import java.util.List; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| import software.amazon.awssdk.core.interceptor.Context; | ||
| import software.amazon.awssdk.core.interceptor.ExecutionAttributes; | ||
| import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; | ||
| import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; | ||
|
|
||
| /** | ||
| * Records invoked operation names. Registered as a global interceptor via | ||
| * {@code software/amazon/awssdk/global/handlers/execution.interceptors} so tests can observe calls made by clients | ||
| * they cannot configure, such as the client a generated {@code SdkWarmUpProvider} builds internally. | ||
| */ | ||
| public class OperationRecordingInterceptor implements ExecutionInterceptor { | ||
|
|
||
| private static final List<String> OPERATION_NAMES = new CopyOnWriteArrayList<>(); | ||
|
|
||
| public static List<String> operationNames() { | ||
| return OPERATION_NAMES; | ||
| } | ||
|
|
||
| public static void reset() { | ||
| OPERATION_NAMES.clear(); | ||
| } | ||
|
|
||
| @Override | ||
| public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { | ||
| OPERATION_NAMES.add(executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| /* | ||
| * Copyright 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. | ||
| */ | ||
|
|
||
| package software.amazon.awssdk.warmup.allservices; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import org.apache.logging.log4j.Level; | ||
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import software.amazon.awssdk.core.crac.SdkWarmUp; | ||
| import software.amazon.awssdk.testutils.LogCaptor; | ||
|
|
||
| /** | ||
| * Tests the customer entry point {@link SdkWarmUp#prime()} with all service modules on the classpath. | ||
| * | ||
| * <p>{@code prime()} catches provider failures and logs them at WARN | ||
| * ({@code "Warm-up failed for <class> and was skipped."}), so the captured logs are the only failure signal here. | ||
| * {@link AllServicesWarmUpTest} checks each provider directly. | ||
| */ | ||
| class SdkWarmUpPrimeAllServicesTest { | ||
|
|
||
| /** | ||
| * TODO: Remove once cloudfrontkeyvaluestore has a warm-up customization. | ||
| * | ||
| * <p>Its warm-up operation takes an ARN, and the service's endpoint rules reject the generic dummy ARN because | ||
| * they require a CloudFront ARN. Until codegen supplies a valid ARN, skip this provider so the bad ARN does not | ||
| * fail the test. | ||
| */ | ||
| private static final String CUSTOMIZATION_REQUIRED_PROVIDER = | ||
| "software.amazon.awssdk.services.cloudfrontkeyvaluestore.internal.crac.CloudFrontKeyValueStoreWarmUpProvider"; | ||
|
|
||
| private String savedRegionProperty; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| savedRegionProperty = System.getProperty("aws.region"); | ||
| System.setProperty("aws.region", "us-east-1"); | ||
| } | ||
|
|
||
| @AfterEach | ||
| void tearDown() { | ||
| if (savedRegionProperty != null) { | ||
| System.setProperty("aws.region", savedRegionProperty); | ||
| } else { | ||
| System.clearProperty("aws.region"); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void prime_withAllServicesOnClasspath_noServiceProviderFails() { | ||
| OperationRecordingInterceptor.reset(); | ||
| try (LogCaptor logCaptor = LogCaptor.create(Level.WARN)) { | ||
| SdkWarmUp.prime(); | ||
|
|
||
| List<String> serviceWarmUpFailures = | ||
| logCaptor.loggedEvents().stream() | ||
| .map(e -> e.getMessage().getFormattedMessage()) | ||
| .filter(msg -> msg.contains("software.amazon")) | ||
| // TODO : Remove after customization is added | ||
| .filter(msg -> !msg.contains(CUSTOMIZATION_REQUIRED_PROVIDER)) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| assertThat(serviceWarmUpFailures) | ||
| .as("SdkWarmUp.prime() must not log a warm-up failure for any generated service provider") | ||
| .isEmpty(); | ||
| } | ||
|
|
||
| // prime() runs once per JVM. If another test in this module called prime() first, this test would see no log | ||
| // events and no recorded operations and pass without checking anything. This assertion catches that case. | ||
| assertThat(OperationRecordingInterceptor.operationNames()) | ||
| .as("prime() must have invoked warm-up operations; if empty, prime() already ran earlier in this JVM " | ||
| + "and this test verified nothing") | ||
| .isNotEmpty(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: technically, this feature/test doesn't directly interact with CRaC, let's do a scan and remove the wording. Can be done in a separate PR