Skip to content

Commit b47d247

Browse files
Fix potentially flaky S3TransferManagerListenerTests (#2801)
* Fix potentially flaky S3TransferManagerListenerTests As they were previously written, these tests were subject to fail due to the following reasons: 1. The execution order of multiple `CompletableFuture#whenComplete(..)` calls is non-deterministic (unless the result of each one is chained together, in sequence) 2. Therefore it was possible that the `CompletableFuture` associated with a transfer may be completed before or after `TransferListener#transferComplete(..)` was invoked 3. While this is not problematic from a public API perspective, our tests assumed that the future completion meant all listeners had been exercised, which may not always be true This change fixes the tests to instead await for the expected interactions to occur, rather than await for the associated future to be completed. An alternative approach would be to sequence the multiple completion stages together, guaranteeing that listeners are invoked before the future is completed, but this may delay the completion of the future in the event of longer-running listener implementations. It seems preferable to favor immediate completion of the future.
1 parent ca51281 commit b47d247

File tree

2 files changed

+23
-15
lines changed

2 files changed

+23
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "S3TransferManager",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fix potentially flaky S3TransferManagerListenerTests"
6+
}

services-custom/s3-transfer-manager/src/test/java/software/amazon/awssdk/transfer/s3/internal/S3TransferManagerListenerTest.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import static org.mockito.Matchers.any;
2121
import static org.mockito.Mockito.doThrow;
2222
import static org.mockito.Mockito.mock;
23-
import static org.mockito.Mockito.times;
23+
import static org.mockito.Mockito.timeout;
2424
import static org.mockito.Mockito.verify;
2525
import static org.mockito.Mockito.verifyNoMoreInteractions;
2626
import static org.mockito.Mockito.when;
@@ -93,33 +93,33 @@ public void upload_success_shouldInvokeListener() throws Exception {
9393
.overrideConfiguration(b -> b.addListener(listener))
9494
.build();
9595
Upload upload = tm.upload(uploadRequest);
96-
upload.completionFuture().join();
9796

9897
ArgumentCaptor<TransferListener.Context.TransferInitiated> captor1 =
9998
ArgumentCaptor.forClass(TransferListener.Context.TransferInitiated.class);
100-
verify(listener, times(1)).transferInitiated(captor1.capture());
99+
verify(listener, timeout(1000).times(1)).transferInitiated(captor1.capture());
101100
TransferListener.Context.TransferInitiated ctx1 = captor1.getValue();
102101
assertThat(ctx1.request()).isSameAs(uploadRequest);
103102
assertThat(ctx1.progressSnapshot().transferSizeInBytes()).hasValue(contentLength);
104103
assertThat(ctx1.progressSnapshot().bytesTransferred()).isZero();
105104

106105
ArgumentCaptor<TransferListener.Context.BytesTransferred> captor2 =
107106
ArgumentCaptor.forClass(TransferListener.Context.BytesTransferred.class);
108-
verify(listener, times(1)).bytesTransferred(captor2.capture());
107+
verify(listener, timeout(1000).times(1)).bytesTransferred(captor2.capture());
109108
TransferListener.Context.BytesTransferred ctx2 = captor2.getValue();
110109
assertThat(ctx2.request()).isSameAs(uploadRequest);
111110
assertThat(ctx2.progressSnapshot().transferSizeInBytes()).hasValue(contentLength);
112111
assertThat(ctx2.progressSnapshot().bytesTransferred()).isPositive();
113112

114113
ArgumentCaptor<TransferListener.Context.TransferComplete> captor3 =
115114
ArgumentCaptor.forClass(TransferListener.Context.TransferComplete.class);
116-
verify(listener, times(1)).transferComplete(captor3.capture());
115+
verify(listener, timeout(1000).times(1)).transferComplete(captor3.capture());
117116
TransferListener.Context.TransferComplete ctx3 = captor3.getValue();
118117
assertThat(ctx3.request()).isSameAs(uploadRequest);
119118
assertThat(ctx3.progressSnapshot().transferSizeInBytes()).hasValue(contentLength);
120119
assertThat(ctx3.progressSnapshot().bytesTransferred()).isEqualTo(contentLength);
121120
assertThat(ctx3.completedTransfer()).isSameAs(upload.completionFuture().get());
122121

122+
upload.completionFuture().join();
123123
verifyNoMoreInteractions(listener);
124124
}
125125

@@ -134,11 +134,10 @@ public void download_success_shouldInvokeListener() throws Exception {
134134
.overrideConfiguration(b -> b.addListener(listener))
135135
.build();
136136
Download download = tm.download(downloadRequest);
137-
download.completionFuture().join();
138137

139138
ArgumentCaptor<TransferListener.Context.TransferInitiated> captor1 =
140139
ArgumentCaptor.forClass(TransferListener.Context.TransferInitiated.class);
141-
verify(listener, times(1)).transferInitiated(captor1.capture());
140+
verify(listener, timeout(1000).times(1)).transferInitiated(captor1.capture());
142141
TransferListener.Context.TransferInitiated ctx1 = captor1.getValue();
143142
assertThat(ctx1.request()).isSameAs(downloadRequest);
144143
// transferSize is not known until we receive GetObjectResponse header
@@ -147,7 +146,7 @@ public void download_success_shouldInvokeListener() throws Exception {
147146

148147
ArgumentCaptor<TransferListener.Context.BytesTransferred> captor2 =
149148
ArgumentCaptor.forClass(TransferListener.Context.BytesTransferred.class);
150-
verify(listener, times(1)).bytesTransferred(captor2.capture());
149+
verify(listener, timeout(1000).times(1)).bytesTransferred(captor2.capture());
151150
TransferListener.Context.BytesTransferred ctx2 = captor2.getValue();
152151
assertThat(ctx2.request()).isSameAs(downloadRequest);
153152
// transferSize should now be known
@@ -156,13 +155,14 @@ public void download_success_shouldInvokeListener() throws Exception {
156155

157156
ArgumentCaptor<TransferListener.Context.TransferComplete> captor3 =
158157
ArgumentCaptor.forClass(TransferListener.Context.TransferComplete.class);
159-
verify(listener, times(1)).transferComplete(captor3.capture());
158+
verify(listener, timeout(1000).times(1)).transferComplete(captor3.capture());
160159
TransferListener.Context.TransferComplete ctx3 = captor3.getValue();
161160
assertThat(ctx3.request()).isSameAs(downloadRequest);
162161
assertThat(ctx3.progressSnapshot().transferSizeInBytes()).hasValue(contentLength);
163162
assertThat(ctx3.progressSnapshot().bytesTransferred()).isEqualTo(contentLength);
164163
assertThat(ctx3.completedTransfer()).isSameAs(download.completionFuture().get());
165164

165+
download.completionFuture().join();
166166
verifyNoMoreInteractions(listener);
167167
}
168168

@@ -188,7 +188,7 @@ public void upload_failure_shouldInvokeListener() throws Exception {
188188

189189
ArgumentCaptor<TransferListener.Context.TransferInitiated> captor1 =
190190
ArgumentCaptor.forClass(TransferListener.Context.TransferInitiated.class);
191-
verify(listener, times(1)).transferInitiated(captor1.capture());
191+
verify(listener, timeout(1000).times(1)).transferInitiated(captor1.capture());
192192
TransferListener.Context.TransferInitiated ctx1 = captor1.getValue();
193193
assertThat(ctx1.request()).isSameAs(uploadRequest);
194194
// transferSize is not known since file did not exist
@@ -197,13 +197,14 @@ public void upload_failure_shouldInvokeListener() throws Exception {
197197

198198
ArgumentCaptor<TransferListener.Context.TransferFailed> captor2 =
199199
ArgumentCaptor.forClass(TransferListener.Context.TransferFailed.class);
200-
verify(listener, times(1)).transferFailed(captor2.capture());
200+
verify(listener, timeout(1000).times(1)).transferFailed(captor2.capture());
201201
TransferListener.Context.TransferFailed ctx2 = captor2.getValue();
202202
assertThat(ctx2.request()).isSameAs(uploadRequest);
203203
assertThat(ctx2.progressSnapshot().transferSizeInBytes()).isNotPresent();
204204
assertThat(ctx2.progressSnapshot().bytesTransferred()).isZero();
205205
assertThat(ctx2.exception()).isInstanceOf(NoSuchFileException.class);
206206

207+
upload.completionFuture().join();
207208
verifyNoMoreInteractions(listener);
208209
}
209210

@@ -221,11 +222,12 @@ public void listener_exception_shouldBeSuppressed() throws Exception {
221222
.overrideConfiguration(b -> b.addListener(listener))
222223
.build();
223224
Upload upload = tm.upload(uploadRequest);
224-
upload.completionFuture().join();
225225

226-
verify(listener, times(1)).transferInitiated(any());
227-
verify(listener, times(1)).bytesTransferred(any());
228-
verify(listener, times(1)).transferComplete(any());
226+
verify(listener, timeout(1000).times(1)).transferInitiated(any());
227+
verify(listener, timeout(1000).times(1)).bytesTransferred(any());
228+
verify(listener, timeout(1000).times(1)).transferComplete(any());
229+
230+
upload.completionFuture().join();
229231
verifyNoMoreInteractions(listener);
230232
}
231233

0 commit comments

Comments
 (0)