Skip to content

Commit

Permalink
Enable BlockHound for integration tests and stability tests (#2863)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Bennett-Lynch authored Nov 23, 2021
1 parent ee3d583 commit a5f7a2e
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<netty-open-ssl-version>2.0.43.Final</netty-open-ssl-version>
<dynamodb-local.version>1.16.0</dynamodb-local.version>
<sqllite.version>1.0.392</sqllite.version>
<blockhound.version>1.0.6.RELEASE</blockhound.version>

<!-- build plugin dependencies-->
<maven.surefire.version>3.0.0-M5</maven.surefire.version>
Expand Down
12 changes: 12 additions & 0 deletions services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,18 @@
<version>${awsjavasdk.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound</artifactId>
<version>${blockhound.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>${blockhound.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>service-test-utils</artifactId>
<groupId>software.amazon.awssdk</groupId>
Expand Down
14 changes: 14 additions & 0 deletions services/s3/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
<ignoredUsedUndeclaredDependencies>
<ignoredUnusedDeclaredDependency>org.junit.jupiter:*</ignoredUnusedDeclaredDependency>
</ignoredUsedUndeclaredDependencies>
</configuration>
</plugin>
</plugins>
</build>

Expand Down Expand Up @@ -117,5 +126,10 @@
<version>${awsjavasdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* BlockHound is installed in one of two ways:
* <ol>
* <li>Using BlockHound's provided {@link BlockHoundTestExecutionListener}, which will be automatically detected by the
* JUnit 5 platform upon initialization.</li>
* <li>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.
* </ol>
* <p>
* 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<Throwable> 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();
}
}
}
13 changes: 13 additions & 0 deletions test/service-test-utils/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound</artifactId>
<version>${blockhound.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>${blockhound.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
Expand All @@ -101,6 +113,7 @@
<ignoredUnusedDeclaredDependency>software.amazon.awssdk:test-utils:*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.junit.jupiter:*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>org.junit.vintage:*</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>io.projectreactor.tools:blockhound-junit-platform:*</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@
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;
import software.amazon.awssdk.core.exception.SdkServiceException;
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";

Expand Down
12 changes: 12 additions & 0 deletions test/stability-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@
<version>${junit5.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound</artifactId>
<version>${blockhound.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound-junit-platform</artifactId>
<version>${blockhound.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down

0 comments on commit a5f7a2e

Please sign in to comment.