Skip to content

Commit f386bfe

Browse files
committed
Refactor tests
1 parent 76d3277 commit f386bfe

File tree

3 files changed

+70
-212
lines changed

3 files changed

+70
-212
lines changed

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/S3MultipartClientGetObjectToBytesWiremockTest.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
import org.junit.jupiter.api.Timeout;
5353
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
5454
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
55-
import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
5655
import software.amazon.awssdk.core.ResponseBytes;
5756
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
5857
import software.amazon.awssdk.core.exception.SdkClientException;
@@ -104,10 +103,7 @@ public void setup(WireMockRuntimeInfo wm) {
104103
.httpClientBuilder(NettyNioAsyncHttpClient.builder().maxConcurrency(100).connectionAcquisitionTimeout(Duration.ofSeconds(100)))
105104
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("key", "secret")))
106105
.overrideConfiguration(
107-
o -> o.retryStrategy(AwsRetryStrategy.standardRetryStrategy().toBuilder()
108-
.maxAttempts(MAX_ATTEMPTS)
109-
.circuitBreakerEnabled(false)
110-
.build())
106+
o -> o.retryStrategy(b -> b.maxAttempts(MAX_ATTEMPTS))
111107
.addExecutionInterceptor(capturingInterceptor))
112108
.build();
113109
}
@@ -151,7 +147,7 @@ public void getObject_single500WithinMany200s_shouldRetrySuccessfully() {
151147
public void getObject_concurrent503s_shouldRetrySuccessfully() {
152148
List<CompletableFuture<ResponseBytes<GetObjectResponse>>> futures = new ArrayList<>();
153149

154-
int numRuns = 1000;
150+
int numRuns = 100;
155151
for (int i = 0; i < numRuns; i++) {
156152
CompletableFuture<ResponseBytes<GetObjectResponse>> resp = mockRetryableErrorThen200Response(multipartClient, i);
157153
futures.add(resp);
@@ -188,7 +184,7 @@ public void getObject_5xxErrorResponses_shouldNotReuseInitialRequestId() {
188184

189185

190186
List<SdkHttpResponse> responses = capturingInterceptor.getResponses();
191-
assertEquals(MAX_ATTEMPTS, responses.size(), () -> String.format("Expected exactly %s responses", MAX_ATTEMPTS));
187+
assertEquals(MAX_ATTEMPTS, responses.size());
192188

193189
String actualFirstRequestId = responses.get(0).firstMatchingHeader("x-amz-request-id").orElse(null);
194190
String actualSecondRequestId = responses.get(1).firstMatchingHeader("x-amz-request-id").orElse(null);
@@ -222,7 +218,7 @@ public void multipartDownload_200Response_shouldSucceed() {
222218

223219
ResponseBytes<GetObjectResponse> response = future.join();
224220
byte[] actualBody = response.asByteArray();
225-
assertArrayEquals(expectedBody, actualBody, "Downloaded body should match expected combined parts");
221+
assertArrayEquals(expectedBody, actualBody);
226222

227223
// Verify that all 3 parts were requested only once
228224
verify(1, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY))));
@@ -336,7 +332,7 @@ public void multipartDownload_503OnFirstPartAndSecondPart_shouldRetrySuccessfull
336332
AsyncResponseTransformer.toBytes()).join();
337333

338334
byte[] actualBody = response.asByteArray();
339-
assertArrayEquals(expectedBody, actualBody, "Downloaded body should match expected combined parts");
335+
assertArrayEquals(expectedBody, actualBody);
340336

341337
// Verify that part 1 was requested twice (initial 503 + retry)
342338
verify(2, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY))));

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/multipart/S3MultipartClientGetObjectWiremockTest.java

Lines changed: 65 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,13 @@
3333
import static software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils.internalErrorBody;
3434
import static software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils.transformersSuppliers;
3535

36+
import com.github.tomakehurst.wiremock.http.Fault;
3637
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
3738
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
3839
import com.github.tomakehurst.wiremock.stubbing.Scenario;
3940
import java.io.IOException;
40-
import java.io.UncheckedIOException;
41+
import java.net.SocketException;
4142
import java.net.URI;
42-
import java.nio.file.Files;
43-
import java.nio.file.Path;
4443
import java.time.Duration;
4544
import java.util.ArrayList;
4645
import java.util.Arrays;
@@ -49,15 +48,12 @@
4948
import java.util.concurrent.CompletableFuture;
5049
import java.util.concurrent.CompletionException;
5150
import java.util.concurrent.TimeUnit;
52-
import java.util.stream.IntStream;
5351
import java.util.stream.Stream;
54-
import org.junit.jupiter.api.Assumptions;
5552
import org.junit.jupiter.api.BeforeEach;
5653
import org.junit.jupiter.api.Timeout;
5754
import org.junit.jupiter.params.ParameterizedTest;
5855
import org.junit.jupiter.params.provider.Arguments;
5956
import org.junit.jupiter.params.provider.MethodSource;
60-
import org.reactivestreams.Subscriber;
6157
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
6258
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
6359
import software.amazon.awssdk.core.SplittingTransformerConfiguration;
@@ -70,7 +66,6 @@
7066
import software.amazon.awssdk.regions.Region;
7167
import software.amazon.awssdk.services.s3.S3AsyncClient;
7268
import software.amazon.awssdk.services.s3.internal.multipart.utils.MultipartDownloadTestUtils;
73-
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
7469
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
7570
import software.amazon.awssdk.services.s3.model.S3Exception;
7671
import software.amazon.awssdk.services.s3.utils.AsyncResponseTransformerTestSupplier;
@@ -82,7 +77,6 @@ public class S3MultipartClientGetObjectWiremockTest {
8277
private static final String BUCKET = "Example-Bucket";
8378
private static final String KEY = "Key";
8479
private static final int MAX_ATTEMPTS = 3;
85-
private static int fileCounter = 0;
8680
private S3AsyncClient multipartClient;
8781
private MultipartDownloadTestUtils util;
8882

@@ -112,10 +106,6 @@ <T> void happyPath_shouldReceiveAllBodyPartInCorrectOrder(AsyncResponseTransform
112106
int partSize) {
113107
byte[] expectedBody = util.stubAllParts(BUCKET, KEY, amountOfPartToTest, partSize);
114108
AsyncResponseTransformer<GetObjectResponse, T> transformer = supplier.transformer();
115-
AsyncResponseTransformer.SplitResult<GetObjectResponse, T> split = transformer.split(
116-
SplittingTransformerConfiguration.builder()
117-
.bufferSizeInBytes(1024 * 32L)
118-
.build());
119109

120110
T response = multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY), transformer).join();
121111

@@ -124,25 +114,6 @@ <T> void happyPath_shouldReceiveAllBodyPartInCorrectOrder(AsyncResponseTransform
124114
util.verifyCorrectAmountOfRequestsMade(amountOfPartToTest);
125115
}
126116

127-
@ParameterizedTest
128-
@MethodSource("partSizeAndTransformerParams")
129-
<T> void nonRetryableErrorOnFirstPart_shouldFail(AsyncResponseTransformerTestSupplier<T> supplier,
130-
int amountOfPartToTest,
131-
int partSize) {
132-
stubFor(get(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY))).willReturn(
133-
aResponse()
134-
.withStatus(400)
135-
.withBody("<Error><Code>400</Code><Message>test error message</Message></Error>")));
136-
AsyncResponseTransformer<GetObjectResponse, T> transformer = supplier.transformer();
137-
AsyncResponseTransformer.SplitResult<GetObjectResponse, T> split = transformer.split(
138-
SplittingTransformerConfiguration.builder()
139-
.bufferSizeInBytes(1024 * 32L)
140-
.build());
141-
142-
assertThatThrownBy(() -> multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY), transformer).join())
143-
.hasMessageContaining("test error message");
144-
}
145-
146117
@ParameterizedTest
147118
@MethodSource("partSizeAndTransformerParams")
148119
<T> void nonRetryableErrorOnThirdPart_shouldCompleteExceptionallyOnlyPartsGreaterThanTwo(
@@ -160,12 +131,6 @@ <T> void nonRetryableErrorOnThirdPart_shouldCompleteExceptionallyOnlyPartsGreate
160131
SplittingTransformerConfiguration.builder()
161132
.bufferSizeInBytes(1024 * 32L)
162133
.build());
163-
Subscriber<AsyncResponseTransformer<GetObjectResponse, GetObjectResponse>> subscriber = new MultipartDownloaderSubscriber(
164-
multipartClient,
165-
GetObjectRequest.builder()
166-
.bucket(BUCKET)
167-
.key(KEY)
168-
.build());
169134

170135
if (partSize > 1) {
171136
assertThatThrownBy(() -> {
@@ -179,116 +144,46 @@ <T> void nonRetryableErrorOnThirdPart_shouldCompleteExceptionallyOnlyPartsGreate
179144
}
180145

181146
@ParameterizedTest
182-
@MethodSource("partSizeAndTransformerParams")
183-
<T> void serverError_retryExhausted_shouldFail(AsyncResponseTransformerTestSupplier<T> supplier,
184-
int amountOfPartToTest,
185-
int partSize) {
186-
util.stubSeverError(1, internalErrorBody(), amountOfPartToTest);
147+
@MethodSource("responseTransformers")
148+
<T> void nonRetryableErrorOnFirstPart_shouldFail(AsyncResponseTransformerTestSupplier<T> supplier) {
149+
stubFor(get(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY))).willReturn(
150+
aResponse()
151+
.withStatus(400)
152+
.withBody("<Error><Code>400</Code><Message>test error message</Message></Error>")));
187153
AsyncResponseTransformer<GetObjectResponse, T> transformer = supplier.transformer();
188154

189-
190-
// Only enable this test for ByteArrayAsyncResponseTransformer because only ByteArrayAsyncResponseTransformer supports
191-
// retry
192-
Assumptions.assumeTrue(transformer instanceof ByteArrayAsyncResponseTransformer);
193-
194155
assertThatThrownBy(() -> multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY), transformer).join())
195-
.hasMessageContaining(" We encountered an internal error");
196-
verify(MAX_ATTEMPTS, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber=1",
197-
BUCKET, KEY))));
156+
.hasMessageContaining("test error message");
198157
}
199158

200159
@ParameterizedTest
201-
@MethodSource("partSizeAndTransformerParams")
202-
<T> void serverError_retrySucceeds_shouldSucceed(AsyncResponseTransformerTestSupplier<T> supplier,
203-
int amountOfPartToTest,
204-
int partSize) {
205-
206-
byte[] expectedBody = util.stubFirst503Second200AllParts(amountOfPartToTest, partSize);
207-
AsyncResponseTransformer<GetObjectResponse, T> transformer = supplier.transformer();
208-
209-
// Only enable this test for ByteArrayAsyncResponseTransformer because only ByteArrayAsyncResponseTransformer supports
210-
// retry
211-
Assumptions.assumeTrue(transformer instanceof ByteArrayAsyncResponseTransformer);
212-
213-
T response = multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY), transformer).join();
214-
215-
byte[] body = supplier.body(response);
216-
assertArrayEquals(expectedBody, body);
217-
util.verifyCorrectAmountOfRequestsMade(amountOfPartToTest);
218-
219-
IntStream.range(1, amountOfPartToTest)
220-
.forEach(index -> verify(2, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber="+ index,
221-
BUCKET, KEY)))));
222-
}
223-
224-
private static Stream<Arguments> partSizeAndTransformerParams() {
225-
// amount of part, individual part size
226-
List<Pair<Integer, Integer>> partSizes = Arrays.asList(
227-
Pair.of(4, 16),
228-
Pair.of(1, 1024),
229-
Pair.of(31, 1243),
230-
Pair.of(16, 16 * 1024),
231-
Pair.of(1, 1024 * 1024),
232-
Pair.of(4, 1024 * 1024),
233-
Pair.of(1, 4 * 1024 * 1024),
234-
Pair.of(4, 6 * 1024 * 1024),
235-
Pair.of(7, 5 * 3752)
236-
);
237-
238-
Stream.Builder<Arguments> sb = Stream.builder();
239-
transformersSuppliers().forEach(tr -> partSizes.forEach(p -> sb.accept(arguments(tr, p.left(), p.right()))));
240-
return sb.build();
241-
}
242-
243-
/**
244-
* Testing {@link PublisherAsyncResponseTransformer}, {@link InputStreamResponseTransformer}, and
245-
* {@link FileAsyncResponseTransformer}
246-
* <p>
247-
*
248-
* Retry for multipart download is supported for {@link ByteArrayAsyncResponseTransformer}, tested in
249-
* {@link S3MultipartClientGetObjectToBytesWiremockTest}.
250-
*/
251-
private static Stream<TransformerFactory> responseTransformerFactories() {
252-
return Stream.of(
253-
AsyncResponseTransformer::toBlockingInputStream,
254-
AsyncResponseTransformer::toPublisher,
255-
() -> {
256-
try {
257-
Path tempDir = Files.createTempDirectory("s3-test");
258-
Path tempFile = tempDir.resolve("testFile" + fileCounter + ".txt");
259-
fileCounter++;
260-
tempFile.toFile().deleteOnExit();
261-
return AsyncResponseTransformer.toFile(tempFile);
262-
} catch (IOException e) {
263-
throw new UncheckedIOException(e);
264-
}
265-
}
266-
);
267-
}
160+
@MethodSource("responseTransformers")
161+
public void ioError_shouldFailAndNotRetry() {
162+
stubFor(get(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY)))
163+
.willReturn(aResponse()
164+
.withFault(Fault.CONNECTION_RESET_BY_PEER)));
268165

269-
interface TransformerFactory {
270-
AsyncResponseTransformer<GetObjectResponse, ?> create();
271-
}
166+
assertThatThrownBy(() -> multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY),
167+
AsyncResponseTransformer.toBlockingInputStream()).join())
168+
.satisfiesAnyOf(
169+
throwable -> assertThat(throwable)
170+
.hasMessageContaining("The connection was closed during the request"),
272171

273-
@ParameterizedTest
274-
@MethodSource("responseTransformerFactories")
275-
public void ioError_shouldFailAndNotRetry(TransformerFactory transformerFactory) {
276-
util.stubIoError( 1);
277-
AsyncResponseTransformer<GetObjectResponse, ?> transformer = transformerFactory.create();
172+
throwable -> assertThat(throwable)
173+
.hasMessageContaining("Connection reset")
174+
);
278175

279-
assertThatThrownBy(() -> multipartClient.getObject(b -> b.bucket(BUCKET).key(KEY), transformer).join())
280-
.hasCauseInstanceOf(IOException.class);
281176
verify(1, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, KEY))));
282177
}
283178

284179
@ParameterizedTest
285-
@MethodSource("responseTransformerFactories")
286-
public void getObject_single500WithinMany200s_shouldNotRetryError(TransformerFactory transformerFactory) {
180+
@MethodSource("responseTransformers")
181+
public void getObject_single500WithinMany200s_shouldNotRetryError(AsyncResponseTransformerTestSupplier<?> transformerSupplier) {
287182
List<CompletableFuture<?>> futures = new ArrayList<>();
288183

289-
int numRuns = 100;
184+
int numRuns = 50;
290185
for (int i = 0; i < numRuns; i++) {
291-
CompletableFuture<?> resp = mock200Response(multipartClient, i, transformerFactory);
186+
CompletableFuture<?> resp = mock200Response(multipartClient, i, transformerSupplier);
292187
futures.add(resp);
293188
}
294189

@@ -311,11 +206,11 @@ public void getObject_single500WithinMany200s_shouldNotRetryError(TransformerFac
311206
.withBody("Hello World")));
312207

313208
CompletableFuture<?> requestWithRetryableError =
314-
multipartClient.getObject(r -> r.bucket(BUCKET).key(errorKey), transformerFactory.create());
209+
multipartClient.getObject(r -> r.bucket(BUCKET).key(errorKey), transformerSupplier.transformer());
315210
futures.add(requestWithRetryableError);
316211

317212
for (int i = 0; i < numRuns; i++) {
318-
CompletableFuture<?> resp = mock200Response(multipartClient, i + 1000, transformerFactory);
213+
CompletableFuture<?> resp = mock200Response(multipartClient, i + 1000, transformerSupplier);
319214
futures.add(resp);
320215
}
321216

@@ -329,7 +224,42 @@ public void getObject_single500WithinMany200s_shouldNotRetryError(TransformerFac
329224
verify(1, getRequestedFor(urlEqualTo(String.format("/%s/%s?partNumber=1", BUCKET, errorKey))));
330225
}
331226

332-
private CompletableFuture<?> mock200Response(S3AsyncClient s3Client, int runNumber, TransformerFactory transformerFactory) {
227+
private static Stream<Arguments> partSizeAndTransformerParams() {
228+
// amount of part, individual part size
229+
List<Pair<Integer, Integer>> partSizes = Arrays.asList(
230+
Pair.of(4, 16),
231+
Pair.of(1, 1024),
232+
Pair.of(31, 1243),
233+
Pair.of(16, 16 * 1024),
234+
Pair.of(1, 1024 * 1024),
235+
Pair.of(4, 1024 * 1024),
236+
Pair.of(1, 4 * 1024 * 1024),
237+
Pair.of(4, 6 * 1024 * 1024),
238+
Pair.of(7, 5 * 3752)
239+
);
240+
241+
Stream.Builder<Arguments> sb = Stream.builder();
242+
transformersSuppliers().forEach(tr -> partSizes.forEach(p -> sb.accept(arguments(tr, p.left(), p.right()))));
243+
return sb.build();
244+
}
245+
246+
247+
/**
248+
* Testing {@link PublisherAsyncResponseTransformer}, {@link InputStreamResponseTransformer}, and
249+
* {@link FileAsyncResponseTransformer}
250+
* <p>
251+
*
252+
* Retry for multipart download is supported for {@link ByteArrayAsyncResponseTransformer}, tested in
253+
* {@link S3MultipartClientGetObjectToBytesWiremockTest}.
254+
*/
255+
private static Stream<AsyncResponseTransformerTestSupplier<?>> responseTransformers() {
256+
return Stream.of(new AsyncResponseTransformerTestSupplier.InputStreamArtSupplier(),
257+
new AsyncResponseTransformerTestSupplier.PublisherArtSupplier(),
258+
new AsyncResponseTransformerTestSupplier.FileArtSupplier());
259+
}
260+
261+
private CompletableFuture<?> mock200Response(S3AsyncClient s3Client, int runNumber,
262+
AsyncResponseTransformerTestSupplier<?> transformerSupplier) {
333263
String runId = runNumber + " success";
334264

335265
stubFor(any(anyUrl())
@@ -342,6 +272,6 @@ private CompletableFuture<?> mock200Response(S3AsyncClient s3Client, int runNumb
342272

343273
return s3Client.getObject(r -> r.bucket(BUCKET).key(KEY)
344274
.overrideConfiguration(c -> c.putHeader("RunNum", runId)),
345-
transformerFactory.create());
275+
transformerSupplier.transformer());
346276
}
347277
}

0 commit comments

Comments
 (0)