From 4778e7e6a3f04ce304a924a40d443f436fe92051 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Thu, 23 Jul 2026 22:14:04 +0000 Subject: [PATCH 1/5] Add test to make sure all the SDK generated services have warmUp providers and are executed withouut any errors --- test/warmup-tests/pom.xml | 47 +++++- .../allservices/AllServicesWarmUpTest.java | 142 ++++++++++++++++++ .../OperationRecordingInterceptor.java | 46 ++++++ .../SdkWarmUpPrimeAllServicesTest.java | 91 +++++++++++ .../WarmUpProviderCompletenessTest.java | 108 +++++++++++++ .../src/test/resources/log4j2.properties | 26 ++++ .../global/handlers/execution.interceptors | 1 + 7 files changed, 458 insertions(+), 3 deletions(-) create mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java create mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java create mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java create mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java create mode 100644 test/warmup-tests/src/test/resources/log4j2.properties create mode 100644 test/warmup-tests/src/test/resources/software/amazon/awssdk/global/handlers/execution.interceptors diff --git a/test/warmup-tests/pom.xml b/test/warmup-tests/pom.xml index d292904c998f..2a762b061839 100644 --- a/test/warmup-tests/pom.xml +++ b/test/warmup-tests/pom.xml @@ -26,9 +26,11 @@ warmup-tests AWS Java SDK :: Test :: Warm-up Tests - 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. + 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. https://aws.amazon.com/sdkforjava @@ -43,6 +45,14 @@ + + + true + + software.amazon.awssdk @@ -55,6 +65,12 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + http-auth-aws-crt + ${awsjavasdk.version} + test + software.amazon.awssdk apache5-client @@ -91,6 +107,31 @@ ${awsjavasdk.version} test + + + software.amazon.awssdk + aws-sdk-java + ${awsjavasdk.version} + test + + + software.amazon.awssdk + test-utils + ${awsjavasdk.version} + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j-impl + test + org.junit.jupiter junit-jupiter diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java new file mode 100644 index 000000000000..94e8db94f48e --- /dev/null +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java @@ -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. + * + *

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 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. + * + *

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 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..internal.crac}, so the filter excludes this module's + * hand-written test providers in {@code software.amazon.awssdk.http.warmup}. + */ + static Stream> 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 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(); + } + } +} diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java new file mode 100644 index 000000000000..9836e11e12f7 --- /dev/null +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java @@ -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 OPERATION_NAMES = new CopyOnWriteArrayList<>(); + + public static List 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)); + } +} diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java new file mode 100644 index 000000000000..55f29433f86c --- /dev/null +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java @@ -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. + * + *

{@code prime()} catches provider failures and logs them at WARN + * ({@code "Warm-up failed for 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. + * + *

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 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(); + } +} diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java new file mode 100644 index 000000000000..c21d16720ff4 --- /dev/null +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java @@ -0,0 +1,108 @@ +/* + * 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.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Optional; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.crac.SdkWarmUpProvider; + +/** + * Asserts that every service jar on the classpath registers a generated {@link SdkWarmUpProvider} in its + * {@code META-INF/services} file. + * + *

{@link AllServicesWarmUpTest} and {@link SdkWarmUpPrimeAllServicesTest} find providers through + * {@code ServiceLoader}, which only sees a provider if its service jar has the registration file. If a new service + * ships without that file, those tests generate no case for it and stay green while the service has no warm-up + * coverage. This test reads the jars directly, so it catches a missing registration. + * + *

Jars are enumerated with {@code ClassLoader.getResources}, not {@code java.class.path}: surefire runs the test + * JVM with a manifest-only booter jar, so {@code java.class.path} lists only {@code surefirebooter*.jar} and scanning + * it would find nothing. + */ +class WarmUpProviderCompletenessTest { + + private static final String PROVIDER_RESOURCE = "META-INF/services/software.amazon.awssdk.core.crac.SdkWarmUpProvider"; + + /** + * The fewest service jars we expect to scan (the repo has ~430). Without it, an empty classpath would make the + * registration check pass with nothing scanned. It is a floor, not an exact count, so it need not change per new + * service. + */ + private static final int MINIMUM_EXPECTED_SERVICE_JARS = 400; + + @Test + void everyServiceJar_registersAWarmUpProvider() throws IOException { + List serviceJars = serviceJarsOnClasspath(); + + List jarsWithoutRegistration = serviceJars.stream() + .filter(jar -> jar.getEntry(PROVIDER_RESOURCE) == null) + .map(JarFile::getName) + .collect(Collectors.toList()); + + assertThat(serviceJars) + .as("service jars found on the classpath") + .hasSizeGreaterThanOrEqualTo(MINIMUM_EXPECTED_SERVICE_JARS); + assertThat(jarsWithoutRegistration) + .as("every service jar must register a generated SdkWarmUpProvider in META-INF/services") + .isEmpty(); + } + + private static List serviceJarsOnClasspath() throws IOException { + List serviceJars = new ArrayList<>(); + Enumeration manifests = classLoader().getResources("META-INF/MANIFEST.MF"); + while (manifests.hasMoreElements()) { + openJar(manifests.nextElement()).filter(WarmUpProviderCompletenessTest::isServiceJar) + .ifPresent(serviceJars::add); + } + return serviceJars; + } + + /** + * The jar behind a classpath manifest URL, or empty for a non-jar URL (such as a directory on the classpath). + * The returned {@link JarFile} is shared and cached by the JVM; the classloader owns it, so it must not be closed. + */ + private static Optional openJar(URL manifest) { + if (!"jar".equals(manifest.getProtocol())) { + return Optional.empty(); + } + try { + return Optional.of(((JarURLConnection) manifest.openConnection()).getJarFile()); + } catch (IOException e) { + // Surefire's transient booter jar can be deleted mid-scan; it is never a service jar, so skip it. + return Optional.empty(); + } + } + + private static boolean isServiceJar(JarFile jar) { + return jar.stream() + .anyMatch(e -> e.getName().startsWith("software/amazon/awssdk/services/") + && e.getName().endsWith(".class")); + } + + private static ClassLoader classLoader() { + return WarmUpProviderCompletenessTest.class.getClassLoader(); + } +} diff --git a/test/warmup-tests/src/test/resources/log4j2.properties b/test/warmup-tests/src/test/resources/log4j2.properties new file mode 100644 index 000000000000..a9839e95d9e0 --- /dev/null +++ b/test/warmup-tests/src/test/resources/log4j2.properties @@ -0,0 +1,26 @@ +# +# 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. +# + +status = warn + +appender.console.type = Console +appender.console.name = ConsoleAppender +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable + +# warn keeps CI output manageable while every generated provider warms up; +# LogCaptor adjusts the root level itself when capturing. +rootLogger.level = warn +rootLogger.appenderRef.stdout.ref = ConsoleAppender diff --git a/test/warmup-tests/src/test/resources/software/amazon/awssdk/global/handlers/execution.interceptors b/test/warmup-tests/src/test/resources/software/amazon/awssdk/global/handlers/execution.interceptors new file mode 100644 index 000000000000..8ac3e1bf150e --- /dev/null +++ b/test/warmup-tests/src/test/resources/software/amazon/awssdk/global/handlers/execution.interceptors @@ -0,0 +1 @@ +software.amazon.awssdk.warmup.allservices.OperationRecordingInterceptor From 9f74501fc5a860701497e63b8d30627f1a3e9668 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Mon, 27 Jul 2026 22:10:19 +0000 Subject: [PATCH 2/5] Handle review comments --- test/warmup-tests/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/warmup-tests/pom.xml b/test/warmup-tests/pom.xml index 2a762b061839..ed45264e4ba1 100644 --- a/test/warmup-tests/pom.xml +++ b/test/warmup-tests/pom.xml @@ -26,11 +26,11 @@ warmup-tests AWS Java SDK :: Test :: Warm-up Tests - 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. + Centralized tests for the warm-up feature across all modules. Covers the SdkWarmUp orchestrator, HTTP-client + warm-up (run against every HTTP client), and 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. https://aws.amazon.com/sdkforjava From 4330ff166755813dbac0776a906c8051bcd1c0a7 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 28 Jul 2026 03:21:19 +0000 Subject: [PATCH 3/5] Add customization clients --- .../SdkWarmUpPrimeAllServicesTest.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java index 55f29433f86c..79c180df1757 100644 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java @@ -18,12 +18,18 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import java.util.Objects; +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.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.crac.SdkWarmUp; +import software.amazon.awssdk.core.crac.SdkWarmUpProvider; import software.amazon.awssdk.testutils.LogCaptor; /** @@ -45,6 +51,22 @@ class SdkWarmUpPrimeAllServicesTest { private static final String CUSTOMIZATION_REQUIRED_PROVIDER = "software.amazon.awssdk.services.cloudfrontkeyvaluestore.internal.crac.CloudFrontKeyValueStoreWarmUpProvider"; + /** + * The client class names {@link SdkWarmUp#prime()} logs for {@link #CUSTOMIZATION_REQUIRED_PROVIDER}. prime() + * reports a warm-up failure with the client class name (the default {@code SdkWarmUpProvider.warmUp()} calls + * {@code WarmUpDiscovery.runSafely(syncClientClassName()/asyncClientClassName(), ...)}), not the provider class + * name, so the failure must be excluded by client name. Derived from the provider so the two stay in sync. + */ + private static final Set CUSTOMIZATION_REQUIRED_CLIENTS = customizationRequiredClients(); + + private static Set customizationRequiredClients() { + return StreamSupport.stream(ServiceLoader.load(SdkWarmUpProvider.class).spliterator(), false) + .filter(p -> p.getClass().getName().equals(CUSTOMIZATION_REQUIRED_PROVIDER)) + .flatMap(p -> Stream.of(p.syncClientClassName(), p.asyncClientClassName())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + private String savedRegionProperty; @BeforeEach @@ -73,7 +95,7 @@ void prime_withAllServicesOnClasspath_noServiceProviderFails() { .map(e -> e.getMessage().getFormattedMessage()) .filter(msg -> msg.contains("software.amazon")) // TODO : Remove after customization is added - .filter(msg -> !msg.contains(CUSTOMIZATION_REQUIRED_PROVIDER)) + .filter(msg -> CUSTOMIZATION_REQUIRED_CLIENTS.stream().noneMatch(msg::contains)) .collect(Collectors.toList()); assertThat(serviceWarmUpFailures) From ab1b00b58454543253833176a0a8725c4e5f4dc2 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 29 Jul 2026 03:51:50 +0000 Subject: [PATCH 4/5] trigerring the codebuild test build to see if the codebuild time increased --- .../amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java index 94e8db94f48e..342201d0b333 100644 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java @@ -38,7 +38,7 @@ /** * 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 + * must complete without throwing any exceptions, must invoke the selected warm-up operation (checked with * {@link OperationRecordingInterceptor}), and must not emit SDK warn or error logs. * *

The {@code aws-sdk-java} aggregate artifact puts all service modules on the classpath and is updated when a new From 8834fee293025678ecfa0d16e8f2efc9a96a2b3f Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 29 Jul 2026 22:01:46 +0000 Subject: [PATCH 5/5] fork test per jvm --- test/warmup-tests/pom.xml | 10 + .../allservices/AllServicesWarmUpTest.java | 196 ++++++++++++------ .../OperationRecordingInterceptor.java | 3 +- .../SdkWarmUpPrimeAllServicesTest.java | 113 ---------- .../WarmUpProviderCompletenessTest.java | 108 ---------- 5 files changed, 141 insertions(+), 289 deletions(-) delete mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java delete mode 100644 test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java diff --git a/test/warmup-tests/pom.xml b/test/warmup-tests/pom.xml index ed45264e4ba1..cbc6f56afc02 100644 --- a/test/warmup-tests/pom.xml +++ b/test/warmup-tests/pom.xml @@ -166,6 +166,16 @@ true + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + + diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java index 342201d0b333..e0a43a9331c4 100644 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/AllServicesWarmUpTest.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; @@ -30,113 +31,176 @@ import org.apache.logging.log4j.Level; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import software.amazon.awssdk.core.ClientType; +import software.amazon.awssdk.core.SdkClient; +import software.amazon.awssdk.core.crac.SdkWarmUp; import software.amazon.awssdk.core.crac.SdkWarmUpProvider; import software.amazon.awssdk.testutils.LogCaptor; +import software.amazon.awssdk.utils.ClassLoaderHelper; /** - * Tests the generated {@link SdkWarmUpProvider} of every service module on the classpath. For each provider, warm-up - * must complete without throwing any exceptions, must invoke the selected warm-up operation (checked with - * {@link OperationRecordingInterceptor}), and must not emit SDK warn or error logs. - * - *

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. + * Warms every service on the classpath through the public API: {@link SdkWarmUp#prime(Class[])} per generated + * client, and {@link SdkWarmUp#prime()} for all of them at once. */ class AllServicesWarmUpTest { + private static final String SERVICES_PACKAGE_PREFIX = "software.amazon.awssdk.services."; + /** - * 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. + * Services with no selectable warm-up operation (all operations are streaming or deprecated); warm-up only + * builds and closes the client. */ - private static final Set 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", + private static final Set KNOWN_NO_OP_SERVICES = new HashSet<>(Arrays.asList( + // All APIs are deprecated + "cloudhsm", + "finspacedata", + "iotthingsgraph", + "lexmodelbuilding", + "proton", // All streaming operations - "software.amazon.awssdk.services.kinesisvideomedia.internal.crac.KinesisVideoMediaWarmUpProvider", - "software.amazon.awssdk.services.sagemakerruntimehttp2.internal.crac.SageMakerRuntimeHttp2WarmUpProvider") - ); + "kinesisvideomedia", + "sagemakerruntimehttp2")); /** - * TODO: Remove once cloudfrontkeyvaluestore has a warm-up customization. - * - *

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. + * Known warm-up failures. Document the reason per entry. */ - private static final Set CUSTOMIZATION_REQUIRED_PROVIDERS = new HashSet<>(Collections.singletonList( - "software.amazon.awssdk.services.cloudfrontkeyvaluestore.internal.crac.CloudFrontKeyValueStoreWarmUpProvider") - ); + private static final Set KNOWN_WARMUP_FAILURE_SERVICES = new HashSet<>(Collections.singletonList( + // Endpoint rules require a real CloudFront ARN; the generated dummy ARN is rejected. + "cloudfrontkeyvaluestore")); @BeforeEach void setUp() { OperationRecordingInterceptor.reset(); } - /** - * All generated service providers. Generated providers live in - * {@code software.amazon.awssdk.services..internal.crac}, so the filter excludes this module's - * hand-written test providers in {@code software.amazon.awssdk.http.warmup}. - */ - static Stream> 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()); + void prime_syncClient_invokesOperationWithoutErrors(SdkWarmUpProvider provider) { + verifyWarmUp(provider, provider.syncClientClassName()); } @ParameterizedTest(name = "{0}") @MethodSource("generatedProviders") - void warmUpClient_async_invokesOperationWithoutErrors(SdkWarmUpProvider provider) { - verifyWarmUp(provider, ClientType.ASYNC, provider.asyncClientClassName()); + void prime_asyncClient_invokesOperationWithoutErrors(SdkWarmUpProvider provider) { + verifyWarmUp(provider, 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"); + @Test + void prime_withAllServicesOnClasspath_noServiceProviderFails() { + String savedRegionProperty = System.getProperty("aws.region"); + System.setProperty("aws.region", "us-east-1"); + try (LogCaptor logCaptor = LogCaptor.create(Level.WARN)) { + SdkWarmUp.prime(); - assumeTrue(!CUSTOMIZATION_REQUIRED_PROVIDERS.contains(provider.getClass().getName()), - () -> provider.getClass().getSimpleName() + " is skipped pending a codegen fix"); + assertThat(serviceWarmUpFailures(logCaptor)) + .as("SdkWarmUp.prime() must not log a warm-up failure for any generated service provider") + .isEmpty(); + } finally { + restoreRegionProperty(savedRegionProperty); + } + // prime() runs once per JVM; an empty recording means it already ran and this test verified nothing. + assertThat(OperationRecordingInterceptor.operationNames()) + .as("prime() must have invoked warm-up operations") + .isNotEmpty(); + } + + private void verifyWarmUp(SdkWarmUpProvider provider, String clientClassName) { + assumeTrue(clientClassName != null, + () -> provider.getClass().getSimpleName() + " does not generate this client type"); + assumeTrue(!KNOWN_WARMUP_FAILURE_SERVICES.contains(serviceName(provider)), + () -> provider.getClass().getSimpleName() + + " is a known warm-up failure; see KNOWN_WARMUP_FAILURE_SERVICES"); + + List sdkWarnings; 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 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(); + SdkWarmUp.prime(clientClass(clientClassName)); + sdkWarnings = sdkWarnings(logCaptor); } - 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. + // prime(Class) logs warm-up failures at WARN instead of throwing. + assertThat(sdkWarnings) + .as("Warm-up of %s must not emit SDK warn/error logs", clientClassName) + .isEmpty(); + assertExpectedOperationRecorded(provider, clientClassName); + } + + private void assertExpectedOperationRecorded(SdkWarmUpProvider provider, String clientClassName) { + if (KNOWN_NO_OP_SERVICES.contains(serviceName(provider))) { assertThat(OperationRecordingInterceptor.operationNames()) - .as("%s is listed in KNOWN_NO_OP_PROVIDERS but recorded an operation; remove the stale entry", + .as("%s is listed in KNOWN_NO_OP_SERVICES 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) + .as("Warm-up of %s must invoke its selected warm-up operation", clientClassName) .isNotEmpty(); } } + + /** + * All generated service providers; excludes this module's hand-written test providers. + */ + static Stream> generatedProviders() { + return serviceProviders().map(p -> Named.of(p.getClass().getSimpleName(), p)); + } + + private static Stream serviceProviders() { + return StreamSupport.stream(ServiceLoader.load(SdkWarmUpProvider.class).spliterator(), false) + .filter(p -> p.getClass().getName().startsWith(SERVICES_PACKAGE_PREFIX)); + } + + private static String serviceName(SdkWarmUpProvider provider) { + String remainder = provider.getClass().getName().substring(SERVICES_PACKAGE_PREFIX.length()); + return remainder.substring(0, remainder.indexOf('.')); + } + + private static Class clientClass(String clientClassName) { + try { + return ClassLoaderHelper.loadClass(clientClassName, SdkWarmUp.class).asSubclass(SdkClient.class); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Client class not on classpath: " + clientClassName, e); + } + } + + /** + * Captured warnings from SDK loggers; warnings from the rest of the classpath are ignored. + */ + private static List sdkWarnings(LogCaptor logCaptor) { + return logCaptor.loggedEvents().stream() + .filter(e -> e.getLoggerName().startsWith("software.amazon.awssdk")) + .map(e -> e.getLoggerName() + " - " + e.getMessage().getFormattedMessage()) + .collect(Collectors.toList()); + } + + /** + * Warm-up failures logged by {@code prime()}, excluding {@link #KNOWN_WARMUP_FAILURE_SERVICES}. prime() reports + * failures by client class name, so known failures are matched by their client names. + */ + private static List serviceWarmUpFailures(LogCaptor logCaptor) { + Set knownFailureClients = knownWarmUpFailureClients(); + return logCaptor.loggedEvents().stream() + .map(e -> e.getMessage().getFormattedMessage()) + .filter(msg -> msg.contains("software.amazon")) + .filter(msg -> knownFailureClients.stream().noneMatch(msg::contains)) + .collect(Collectors.toList()); + } + + private static Set knownWarmUpFailureClients() { + return serviceProviders().filter(p -> KNOWN_WARMUP_FAILURE_SERVICES.contains(serviceName(p))) + .flatMap(p -> Stream.of(p.syncClientClassName(), p.asyncClientClassName())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + private static void restoreRegionProperty(String savedValue) { + if (savedValue != null) { + System.setProperty("aws.region", savedValue); + } else { + System.clearProperty("aws.region"); + } + } } diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java index 9836e11e12f7..cafc7d836ca8 100644 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java +++ b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/OperationRecordingInterceptor.java @@ -24,8 +24,7 @@ /** * 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. + * {@code software/amazon/awssdk/global/handlers/execution.interceptors} to observe clients built inside providers. */ public class OperationRecordingInterceptor implements ExecutionInterceptor { diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java deleted file mode 100644 index 79c180df1757..000000000000 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/SdkWarmUpPrimeAllServicesTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.Objects; -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.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.core.crac.SdkWarmUp; -import software.amazon.awssdk.core.crac.SdkWarmUpProvider; -import software.amazon.awssdk.testutils.LogCaptor; - -/** - * Tests the customer entry point {@link SdkWarmUp#prime()} with all service modules on the classpath. - * - *

{@code prime()} catches provider failures and logs them at WARN - * ({@code "Warm-up failed for 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. - * - *

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"; - - /** - * The client class names {@link SdkWarmUp#prime()} logs for {@link #CUSTOMIZATION_REQUIRED_PROVIDER}. prime() - * reports a warm-up failure with the client class name (the default {@code SdkWarmUpProvider.warmUp()} calls - * {@code WarmUpDiscovery.runSafely(syncClientClassName()/asyncClientClassName(), ...)}), not the provider class - * name, so the failure must be excluded by client name. Derived from the provider so the two stay in sync. - */ - private static final Set CUSTOMIZATION_REQUIRED_CLIENTS = customizationRequiredClients(); - - private static Set customizationRequiredClients() { - return StreamSupport.stream(ServiceLoader.load(SdkWarmUpProvider.class).spliterator(), false) - .filter(p -> p.getClass().getName().equals(CUSTOMIZATION_REQUIRED_PROVIDER)) - .flatMap(p -> Stream.of(p.syncClientClassName(), p.asyncClientClassName())) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } - - 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 serviceWarmUpFailures = - logCaptor.loggedEvents().stream() - .map(e -> e.getMessage().getFormattedMessage()) - .filter(msg -> msg.contains("software.amazon")) - // TODO : Remove after customization is added - .filter(msg -> CUSTOMIZATION_REQUIRED_CLIENTS.stream().noneMatch(msg::contains)) - .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(); - } -} diff --git a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java b/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java deleted file mode 100644 index c21d16720ff4..000000000000 --- a/test/warmup-tests/src/test/java/software/amazon/awssdk/warmup/allservices/WarmUpProviderCompletenessTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.io.IOException; -import java.net.JarURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.Optional; -import java.util.jar.JarFile; -import java.util.stream.Collectors; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.core.crac.SdkWarmUpProvider; - -/** - * Asserts that every service jar on the classpath registers a generated {@link SdkWarmUpProvider} in its - * {@code META-INF/services} file. - * - *

{@link AllServicesWarmUpTest} and {@link SdkWarmUpPrimeAllServicesTest} find providers through - * {@code ServiceLoader}, which only sees a provider if its service jar has the registration file. If a new service - * ships without that file, those tests generate no case for it and stay green while the service has no warm-up - * coverage. This test reads the jars directly, so it catches a missing registration. - * - *

Jars are enumerated with {@code ClassLoader.getResources}, not {@code java.class.path}: surefire runs the test - * JVM with a manifest-only booter jar, so {@code java.class.path} lists only {@code surefirebooter*.jar} and scanning - * it would find nothing. - */ -class WarmUpProviderCompletenessTest { - - private static final String PROVIDER_RESOURCE = "META-INF/services/software.amazon.awssdk.core.crac.SdkWarmUpProvider"; - - /** - * The fewest service jars we expect to scan (the repo has ~430). Without it, an empty classpath would make the - * registration check pass with nothing scanned. It is a floor, not an exact count, so it need not change per new - * service. - */ - private static final int MINIMUM_EXPECTED_SERVICE_JARS = 400; - - @Test - void everyServiceJar_registersAWarmUpProvider() throws IOException { - List serviceJars = serviceJarsOnClasspath(); - - List jarsWithoutRegistration = serviceJars.stream() - .filter(jar -> jar.getEntry(PROVIDER_RESOURCE) == null) - .map(JarFile::getName) - .collect(Collectors.toList()); - - assertThat(serviceJars) - .as("service jars found on the classpath") - .hasSizeGreaterThanOrEqualTo(MINIMUM_EXPECTED_SERVICE_JARS); - assertThat(jarsWithoutRegistration) - .as("every service jar must register a generated SdkWarmUpProvider in META-INF/services") - .isEmpty(); - } - - private static List serviceJarsOnClasspath() throws IOException { - List serviceJars = new ArrayList<>(); - Enumeration manifests = classLoader().getResources("META-INF/MANIFEST.MF"); - while (manifests.hasMoreElements()) { - openJar(manifests.nextElement()).filter(WarmUpProviderCompletenessTest::isServiceJar) - .ifPresent(serviceJars::add); - } - return serviceJars; - } - - /** - * The jar behind a classpath manifest URL, or empty for a non-jar URL (such as a directory on the classpath). - * The returned {@link JarFile} is shared and cached by the JVM; the classloader owns it, so it must not be closed. - */ - private static Optional openJar(URL manifest) { - if (!"jar".equals(manifest.getProtocol())) { - return Optional.empty(); - } - try { - return Optional.of(((JarURLConnection) manifest.openConnection()).getJarFile()); - } catch (IOException e) { - // Surefire's transient booter jar can be deleted mid-scan; it is never a service jar, so skip it. - return Optional.empty(); - } - } - - private static boolean isServiceJar(JarFile jar) { - return jar.stream() - .anyMatch(e -> e.getName().startsWith("software/amazon/awssdk/services/") - && e.getName().endsWith(".class")); - } - - private static ClassLoader classLoader() { - return WarmUpProviderCompletenessTest.class.getClassLoader(); - } -}