From a5f7a2e1e2fcee9d486645b5f0dd58de62874a60 Mon Sep 17 00:00:00 2001 From: Bennett Lynch Date: Tue, 23 Nov 2021 12:25:07 -0800 Subject: [PATCH] Enable BlockHound for integration tests and stability tests (#2863) * Enable BlockHound for integration tests and stability tests This PR enables [BlockHound](https://github.com/reactor/BlockHound) for the SDK integration test and stability tests. BlockHound can be used to detect blocking behavior in threads that must be treated as non-blocking, like Netty's EventLoop threads. If BlockHound detects an illegal blocking operation being performed, it will throw a BlockingOperationError. Doing so in an integration test would typically fail the integration test and allow us to investigate the behavior. This PR enables BlockHound by leveraging BlockHound's provided [BlockHoundTestExecutionListener](https://github .com/reactor/BlockHound/blob/2071ca8dbb06eba9ffa65596f327e3182d3b9730 /junit-platform/src/main/java/reactor/blockhound/junit/platform /BlockHoundTestExecutionListener.java), which uses @AutoService to automatically register with JUnit 5's ServiceLoader support: https://junit.org/junit5/docs/current/user-guide/#launcher-api-listeners-custom --- pom.xml | 1 + services/pom.xml | 12 ++ services/s3/pom.xml | 14 ++ .../services/BlockHoundInstalledTest.java | 131 ++++++++++++++++++ test/service-test-utils/pom.xml | 13 ++ .../service/AwsIntegrationTestBase.java | 5 + .../awssdk/testutils/service/AwsTestBase.java | 6 + test/stability-tests/pom.xml | 12 ++ 8 files changed, 194 insertions(+) create mode 100644 services/s3/src/it/java/software/amazon/awssdk/services/BlockHoundInstalledTest.java diff --git a/pom.xml b/pom.xml index fe7c0a5b6498..f979d7d1068d 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,7 @@ 2.0.43.Final 1.16.0 1.0.392 + 1.0.6.RELEASE 3.0.0-M5 diff --git a/services/pom.xml b/services/pom.xml index a545167e113d..5b1d7446cc51 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -377,6 +377,18 @@ ${awsjavasdk.version} runtime + + io.projectreactor.tools + blockhound + ${blockhound.version} + test + + + io.projectreactor.tools + blockhound-junit-platform + ${blockhound.version} + test + service-test-utils software.amazon.awssdk diff --git a/services/s3/pom.xml b/services/s3/pom.xml index 0e9ac199079d..76d84c5932f9 100644 --- a/services/s3/pom.xml +++ b/services/s3/pom.xml @@ -42,6 +42,15 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + org.junit.jupiter:* + + + @@ -117,5 +126,10 @@ ${awsjavasdk.version} test + + io.netty + netty-transport + test + diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/BlockHoundInstalledTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/BlockHoundInstalledTest.java new file mode 100644 index 000000000000..a9f7c318afd1 --- /dev/null +++ b/services/s3/src/it/java/software/amazon/awssdk/services/BlockHoundInstalledTest.java @@ -0,0 +1,131 @@ +/* + * 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.services; + + +import static org.assertj.core.api.Assertions.assertThat; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.blockhound.BlockHound; +import reactor.blockhound.BlockingOperationError; +import reactor.blockhound.integration.BlockHoundIntegration; +import reactor.blockhound.junit.platform.BlockHoundTestExecutionListener; +import software.amazon.awssdk.testutils.service.AwsIntegrationTestBase; +import software.amazon.awssdk.testutils.service.AwsTestBase; +import software.amazon.awssdk.utils.Logger; + +/** + * This test ensures that BlockHound is correctly installed for integration tests. The test is somewhat arbitrarily placed in the + * {@code s3} module in order to assert against the configuration of all service integration tests. + *

+ * BlockHound is installed in one of two ways: + *

    + *
  1. Using BlockHound's provided {@link BlockHoundTestExecutionListener}, which will be automatically detected by the + * JUnit 5 platform upon initialization.
  2. + *
  3. Manually calling {@link BlockHound#install(BlockHoundIntegration...)}. This is done as part of static initialization in + * {@link AwsIntegrationTestBase} and {@link AwsTestBase}, which most integration/stability tests extend. + *
+ *

+ * This test ensures BlockHound is correctly installed by intentionally performing a blocking operation on the Netty + * {@link EventLoop} and asserting that a {@link BlockingOperationError} is thrown to forbid it. + */ +class BlockHoundInstalledTest { + private static final Logger log = Logger.loggerFor(BlockHoundInstalledTest.class); + + AtomicReference throwableReference; + CountDownLatch latch; + EventLoopGroup bossGroup; + EventLoopGroup workerGroup; + Socket clientSocket; + Channel serverChannel; + + @BeforeEach + public void setup() { + throwableReference = new AtomicReference<>(); + latch = new CountDownLatch(1); + bossGroup = new NioEventLoopGroup(); + workerGroup = new NioEventLoopGroup(); + } + + @AfterEach + public void teardown() throws Exception { + if (clientSocket != null) { + clientSocket.close(); + } + if (serverChannel != null) { + serverChannel.close(); + } + workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + + @Test + void testBlockHoundInstalled() throws Exception { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelDuplexHandler() { + @Override + public void channelActive(ChannelHandlerContext ctx) { + log.info(() -> "Preparing to sleep on the EventLoop to test if BlockHound is installed"); + try { + Thread.sleep(1000); + log.info(() -> "BlockHound does not appear to be successfully installed"); + } catch (Throwable t) { + log.info(() -> "BlockHound is successfully installed", t); + throwableReference.set(t); + } + latch.countDown(); + ctx.fireChannelActive(); + } + }); + + int port = getUnusedPort(); + serverChannel = bootstrap.bind(port).sync().channel(); + clientSocket = new Socket("localhost", port); + + latch.await(5, TimeUnit.SECONDS); + assertThat(throwableReference.get()) + .withFailMessage("BlockHound does not appear to be successfully installed. Ensure that either BlockHound.install() " + + "is called prior to all test executions or that BlockHoundTestExecutionListener is available on " + + "the class path and correctly detected by JUnit: " + + "https://github.com/reactor/BlockHound/blob/master/docs/supported_testing_frameworks.md") + .isInstanceOf(BlockingOperationError.class); + } + + private static int getUnusedPort() throws IOException { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } + } +} diff --git a/test/service-test-utils/pom.xml b/test/service-test-utils/pom.xml index a8c6468303e1..e6f7214afd59 100644 --- a/test/service-test-utils/pom.xml +++ b/test/service-test-utils/pom.xml @@ -83,6 +83,18 @@ junit compile + + io.projectreactor.tools + blockhound + ${blockhound.version} + compile + + + io.projectreactor.tools + blockhound-junit-platform + ${blockhound.version} + compile + org.hamcrest hamcrest-core @@ -101,6 +113,7 @@ software.amazon.awssdk:test-utils:* org.junit.jupiter:* org.junit.vintage:* + io.projectreactor.tools:blockhound-junit-platform:* diff --git a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java index 01a8a1738615..2b9de9dd2b03 100644 --- a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java +++ b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsIntegrationTestBase.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; +import reactor.blockhound.BlockHound; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain; @@ -27,6 +28,10 @@ public abstract class AwsIntegrationTestBase { + static { + BlockHound.install(); + } + /** Default Properties Credentials file path. */ private static final String TEST_CREDENTIALS_PROFILE_NAME = "aws-test-account"; diff --git a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsTestBase.java b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsTestBase.java index 1186728f2604..d6c9f6b13c46 100644 --- a/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsTestBase.java +++ b/test/service-test-utils/src/main/java/software/amazon/awssdk/testutils/service/AwsTestBase.java @@ -23,6 +23,7 @@ import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; +import reactor.blockhound.BlockHound; import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; @@ -30,6 +31,11 @@ import software.amazon.awssdk.utils.IoUtils; public abstract class AwsTestBase { + + static { + BlockHound.install(); + } + /** Default Properties Credentials file path. */ private static final String TEST_CREDENTIALS_PROFILE_NAME = "aws-test-account"; diff --git a/test/stability-tests/pom.xml b/test/stability-tests/pom.xml index b3460b391466..65504ba75653 100644 --- a/test/stability-tests/pom.xml +++ b/test/stability-tests/pom.xml @@ -153,6 +153,18 @@ ${junit5.version} test + + io.projectreactor.tools + blockhound + ${blockhound.version} + test + + + io.projectreactor.tools + blockhound-junit-platform + ${blockhound.version} + test + org.assertj assertj-core