diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java index 3bdebad70a42..23d129bd597d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java @@ -70,7 +70,7 @@ protected RequestOverrideConfiguration(Builder builder) { this.endpointProvider = builder.endpointProvider(); this.compressionConfiguration = builder.compressionConfiguration(); this.plugins = Collections.unmodifiableList(new ArrayList<>(builder.plugins())); - this.progressListeners = Collections.unmodifiableList(new ArrayList<>()); + this.progressListeners = Collections.unmodifiableList(new ArrayList<>(builder.progressListeners())); } /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java index 141ba473986f..01cc3e0c5166 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java @@ -15,11 +15,13 @@ package software.amazon.awssdk.core.http; +import java.util.Optional; import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkProtectedApi; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain; import software.amazon.awssdk.core.interceptor.InterceptorContext; +import software.amazon.awssdk.core.internal.progress.listener.ProgressUpdater; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -36,6 +38,7 @@ public final class ExecutionContext implements ToCopyableBuilder progressUpdater() { + return progressUpdater != null ? Optional.of(progressUpdater) : Optional.empty(); + } + @Override public Builder toBuilder() { return new Builder(this); @@ -87,6 +95,7 @@ public static class Builder implements CopyableBuilder { private ProgressListenerContext progressListenerContext; private Throwable exception; diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkRequestProgress.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkExchangeProgress.java similarity index 84% rename from core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkRequestProgress.java rename to core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkExchangeProgress.java index 49724fc16d0f..a7af7a3ab69c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkRequestProgress.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/DefaultSdkExchangeProgress.java @@ -21,24 +21,24 @@ import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.internal.progress.snapshot.DefaultProgressSnapshot; -import software.amazon.awssdk.core.progress.listener.SdkRequestProgress; +import software.amazon.awssdk.core.progress.listener.SdkExchangeProgress; import software.amazon.awssdk.core.progress.snapshot.ProgressSnapshot; /** - * An SDK-internal implementation of {@link SdkRequestProgress}. This implementation acts as a thin wrapper around {@link + * An SDK-internal implementation of {@link SdkExchangeProgress}. This implementation acts as a thin wrapper around {@link * AtomicReference}, where calls to get the latest {@link #progressSnapshot()} simply return the latest reference, while {@link * ProgressUpdater} is responsible for continuously updating the latest reference. * - * @see SdkRequestProgress + * @see SdkExchangeProgress */ @Mutable @ThreadSafe @SdkInternalApi -public class DefaultSdkRequestProgress implements SdkRequestProgress { +public class DefaultSdkExchangeProgress implements SdkExchangeProgress { private final AtomicReference snapshot; - public DefaultSdkRequestProgress(ProgressSnapshot snapshot) { + public DefaultSdkExchangeProgress(ProgressSnapshot snapshot) { this.snapshot = new AtomicReference<>(snapshot); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListener.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListener.java new file mode 100644 index 000000000000..fe75efcae75d --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListener.java @@ -0,0 +1,122 @@ +/* + * 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.core.internal.progress.listener; + +import static software.amazon.awssdk.utils.StringUtils.repeat; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.core.progress.listener.ProgressListener; +import software.amazon.awssdk.utils.Logger; + +/** + * An implementation of {@link ProgressListener} that logs a progress bar at the {@code INFO} level for upload operations. This + * implementation effectively limits the frequency of updates by limiting logging to events of progress advancement. By default, + * the progress bar has {@value #DEFAULT_MAX_TICKS} ticks, meaning an update is logged for every 5% progression, at most. + */ +@SdkPublicApi +public final class LoggingProgressListener implements ProgressListener { + private static final Logger log = Logger.loggerFor(LoggingProgressListener.class); + private static final int DEFAULT_MAX_TICKS = 20; + private final ProgressBar progressBar; + + private LoggingProgressListener(int maxTicks) { + progressBar = new ProgressBar(maxTicks); + } + + /** + * Create an instance of {@link LoggingProgressListener} with a custom {@code maxTicks} value. + * + * @param maxTicks the number of ticks in the logged progress bar + */ + public static LoggingProgressListener create(int maxTicks) { + return new LoggingProgressListener(maxTicks); + } + + /** + * Create an instance of {@link LoggingProgressListener} with the default configuration. + */ + public static LoggingProgressListener create() { + return new LoggingProgressListener(DEFAULT_MAX_TICKS); + } + + + @Override + public void requestPrepared(Context.RequestPrepared context) { + log.info(() -> "Request Prepared..."); + context.uploadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void requestHeaderSent(Context.RequestHeaderSent context) { + context.uploadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void requestBytesSent(Context.RequestBytesSent context) { + context.uploadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void responseHeaderReceived(Context.ResponseHeaderReceived context) { + log.info(() -> "Upload Successful! Starting Download..."); + context.downloadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void responseBytesReceived(Context.ResponseBytesReceived context) { + context.downloadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void executionSuccess(Context.ExecutionSuccess context) { + log.info(() -> "Execution Successful!"); + context.downloadProgressSnapshot().ratioTransferred().ifPresent(progressBar::update); + } + + @Override + public void executionFailure(Context.ExecutionFailure context) { + log.warn(() -> "Execution Failed!", context.exception()); + } + + private static class ProgressBar { + private final int maxTicks; + private final AtomicInteger prevTicks = new AtomicInteger(-1); + + private ProgressBar(int maxTicks) { + this.maxTicks = maxTicks; + } + + void update(double ratio) { + int ticks = (int) Math.floor(ratio * maxTicks); + if (prevTicks.getAndSet(ticks) != ticks) { + log.info(() -> String.format("|%s%s| %s", + repeat("=", ticks), + repeat(" ", maxTicks - ticks), + round(ratio * 100, 1) + "%")); + } + } + + private static double round(double value, int places) { + BigDecimal bd = BigDecimal.valueOf(value); + bd = bd.setScale(places, RoundingMode.FLOOR); + return bd.doubleValue(); + } + } + +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressListenerInvoker.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressListenerInvoker.java index 0caa17930b07..157eca5c449f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressListenerInvoker.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressListenerInvoker.java @@ -78,12 +78,12 @@ public void responseBytesReceived(Context.ResponseBytesReceived context) { } @Override - public void attemptFailure(Context.AttemptFailure context) { + public void attemptFailure(Context.ExecutionFailure context) { forEach(listener -> listener.attemptFailure(context)); } @Override - public void attemptFailureResponseBytesReceived(Context.AttemptFailureResponseBytesReceived context) { + public void attemptFailureResponseBytesReceived(Context.ExecutionFailure context) { forEach(listener -> listener.attemptFailureResponseBytesReceived(context)); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdater.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdater.java new file mode 100644 index 000000000000..3db626977b6a --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdater.java @@ -0,0 +1,157 @@ +/* + * 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.core.internal.progress.listener; + +import java.util.Collections; +import java.util.Optional; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.internal.progress.ProgressListenerContext; +import software.amazon.awssdk.core.internal.progress.ProgressListenerFailedContext; +import software.amazon.awssdk.core.internal.progress.snapshot.DefaultProgressSnapshot; +import software.amazon.awssdk.core.progress.listener.SdkExchangeProgress; +import software.amazon.awssdk.core.progress.snapshot.ProgressSnapshot; + +/** + * ProgressUpdater exposes methods that invokes listener methods to update and store request progress state + */ +@SdkInternalApi +public class ProgressUpdater { + private final DefaultSdkExchangeProgress requestBodyProgress; + private final DefaultSdkExchangeProgress responseBodyProgress; + private ProgressListenerContext context; + private final ProgressListenerInvoker listenerInvoker; + + public ProgressUpdater(SdkRequest sdkRequest, + Long requestContentLength) { + DefaultProgressSnapshot.Builder uploadProgressSnapshotBuilder = DefaultProgressSnapshot.builder(); + uploadProgressSnapshotBuilder.transferredBytes(0L); + Optional.ofNullable(requestContentLength).ifPresent(uploadProgressSnapshotBuilder::totalBytes); + + ProgressSnapshot uploadProgressSnapshot = uploadProgressSnapshotBuilder.build(); + requestBodyProgress = new DefaultSdkExchangeProgress(uploadProgressSnapshot); + + DefaultProgressSnapshot.Builder downloadProgressSnapshotBuilder = DefaultProgressSnapshot.builder(); + downloadProgressSnapshotBuilder.transferredBytes(0L); + ProgressSnapshot downloadProgressSnapshot = downloadProgressSnapshotBuilder.build(); + responseBodyProgress = new DefaultSdkExchangeProgress(downloadProgressSnapshot); + + context = ProgressListenerContext.builder() + .request(sdkRequest) + .uploadProgressSnapshot(uploadProgressSnapshot) + .downloadProgressSnapshot(downloadProgressSnapshot) + .build(); + + listenerInvoker = new ProgressListenerInvoker(sdkRequest.overrideConfiguration() + .map(RequestOverrideConfiguration::progressListeners) + .orElse(Collections.emptyList())); + } + + public void updateResponseContentLength(Long responseContentLength) { + responseBodyProgress.updateAndGet(b -> b.totalBytes(responseContentLength)); + } + + public SdkExchangeProgress requestBodyProgress() { + return requestBodyProgress; + } + + public SdkExchangeProgress responseBodyProgress() { + return responseBodyProgress; + } + + public void requestPrepared() { + listenerInvoker.requestPrepared(context); + } + + public void requestHeaderSent() { + listenerInvoker.requestHeaderSent(context); + } + + public void resetBytesSent() { + requestBodyProgress.updateAndGet(b -> b.transferredBytes(0L)); + } + + public void resetBytesReceived() { + responseBodyProgress.updateAndGet(b -> b.transferredBytes(0L)); + } + + public void incrementBytesSent(long numBytes) { + long uploadBytes = requestBodyProgress.progressSnapshot().transferredBytes(); + + ProgressSnapshot snapshot = requestBodyProgress.updateAndGet(b -> b.transferredBytes(uploadBytes + numBytes)); + listenerInvoker.requestBytesSent(context.copy(b -> b.uploadProgressSnapshot(snapshot))); + } + + public void incrementBytesReceived(long numBytes) { + long downloadedBytes = responseBodyProgress.progressSnapshot().transferredBytes(); + + ProgressSnapshot snapshot = responseBodyProgress.updateAndGet(b -> b.transferredBytes(downloadedBytes + numBytes)); + listenerInvoker.responseBytesReceived(context.copy(b -> b.downloadProgressSnapshot(snapshot))); + } + + public void responseHeaderReceived() { + listenerInvoker.responseHeaderReceived(context); + } + + public void executionSuccess(SdkResponse response) { + + listenerInvoker.executionSuccess(context.copy(b -> b.response(response))); + } + + public void executionFailure(Throwable t) { + listenerInvoker.executionFailure(ProgressListenerFailedContext.builder() + .progressListenerContext( + context.copy( + b -> { + b.uploadProgressSnapshot( + requestBodyProgress.progressSnapshot()); + b.downloadProgressSnapshot( + responseBodyProgress.progressSnapshot()); + })) + .exception(t) + .build()); + } + + public void attemptFailure(Throwable t) { + listenerInvoker.attemptFailure(ProgressListenerFailedContext.builder() + .progressListenerContext( + context.copy( + b -> { + b.uploadProgressSnapshot( + requestBodyProgress.progressSnapshot()); + b.downloadProgressSnapshot( + responseBodyProgress.progressSnapshot()); + })) + .exception(t) + .build()); + } + + public void attemptFailureResponseBytesReceived(Throwable t) { + listenerInvoker.attemptFailureResponseBytesReceived(ProgressListenerFailedContext.builder() + .progressListenerContext( + context.copy( + b -> { + b.uploadProgressSnapshot( + requestBodyProgress.progressSnapshot()); + b.downloadProgressSnapshot( + responseBodyProgress.progressSnapshot()); + })) + .exception(t) + .build()); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshot.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshot.java index 86e9a64004d2..be95d5232b86 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshot.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshot.java @@ -52,7 +52,6 @@ public DefaultProgressSnapshot(Builder builder) { this.transferredBytes = Validate.isNotNegative(builder.transferredBytes, "transferredBytes"); this.totalBytes = builder.totalBytes; - Validate.paramNotNull(builder.startTime, "startTime"); this.startTime = builder.startTime; } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/ProgressListener.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/ProgressListener.java index 3cdfa494226b..4a2e2a4df24d 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/ProgressListener.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/ProgressListener.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.internal.progress.listener.LoggingProgressListener; import software.amazon.awssdk.core.progress.snapshot.ProgressSnapshot; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.SdkHttpResponse; @@ -59,7 +60,7 @@ *
  • {@link #executionSuccess(Context.ExecutionSuccess)} - The transfer has completed successfully. This method is called * for every successful transfer.
  • * - * For every failed attempt {@link #attemptFailure(Context.AttemptFailure)}. + * For every failed attempt {@link #attemptFailure(Context.ExecutionFailure)}. * *

    * There are a few important rules and best practices that govern the usage of {@link ProgressListener}s: @@ -193,15 +194,15 @@ default void responseBytesReceived(Context.ResponseBytesReceived context) { *

    * Available context attributes: *

      - *
    1. {@link Context.AttemptFailureResponseBytesReceived#request()}
    2. - *
    3. {@link Context.AttemptFailureResponseBytesReceived#httpRequest()}
    4. - *
    5. {@link Context.AttemptFailureResponseBytesReceived#uploadProgressSnapshot()}
    6. - *
    7. {@link Context.AttemptFailureResponseBytesReceived#httpResponse()}
    8. - *
    9. {@link Context.AttemptFailureResponseBytesReceived#downloadProgressSnapshot()}
    10. - *
    11. {@link Context.AttemptFailureResponseBytesReceived#exception()}
    12. + *
    13. {@link Context.ExecutionFailure#request()}
    14. + *
    15. {@link Context.ExecutionFailure#httpRequest()}
    16. + *
    17. {@link Context.ExecutionFailure#uploadProgressSnapshot()}
    18. + *
    19. {@link Context.ExecutionFailure#httpResponse()} ()}
    20. + *
    21. {@link Context.ExecutionFailure#downloadProgressSnapshot()}
    22. + *
    23. {@link Context.ExecutionFailure#exception()}
    24. *
    */ - default void attemptFailureResponseBytesReceived(Context.AttemptFailureResponseBytesReceived context) { + default void attemptFailureResponseBytesReceived(Context.ExecutionFailure context) { } /** @@ -229,13 +230,15 @@ default void executionSuccess(Context.ExecutionSuccess context) { *

    * Available context attributes: *

      - *
    1. {@link Context.AttemptFailure#request()}
    2. - *
    3. {@link Context.AttemptFailure#httpRequest()}
    4. - *
    5. {@link Context.AttemptFailure#uploadProgressSnapshot()}
    6. - *
    7. {@link Context.AttemptFailure#exception()} ()}
    8. + *
    9. {@link Context.ExecutionFailure#request()}
    10. + *
    11. {@link Context.ExecutionFailure#httpRequest()}
    12. + *
    13. {@link Context.ExecutionFailure#uploadProgressSnapshot()}
    14. + *
    15. {@link Context.ExecutionFailure#httpResponse()} ()}
    16. + *
    17. {@link Context.ExecutionFailure#downloadProgressSnapshot()}
    18. + *
    19. {@link Context.ExecutionFailure#exception()}
    20. *
    */ - default void attemptFailure(Context.AttemptFailure context) { + default void attemptFailure(Context.ExecutionFailure context) { } /** @@ -247,7 +250,9 @@ default void attemptFailure(Context.AttemptFailure context) { *
  • {@link Context.ExecutionFailure#request()}
  • *
  • {@link Context.ExecutionFailure#httpRequest()}
  • *
  • {@link Context.ExecutionFailure#uploadProgressSnapshot()}
  • - *
  • {@link Context.ExecutionFailure#exception()} ()}
  • + *
  • {@link Context.ExecutionFailure#httpResponse()} ()}
  • + *
  • {@link Context.ExecutionFailure#downloadProgressSnapshot()}
  • + *
  • {@link Context.ExecutionFailure#exception()}
  • * */ default void executionFailure(Context.ExecutionFailure context) { @@ -268,7 +273,6 @@ default void executionFailure(Context.ExecutionFailure context) { * Failed transfer method hierarchy: *
      *
    1. {@link RequestPrepared}
    2. - *
    3. {@link AttemptFailure}
    4. *
    5. {@link ExecutionFailure}
    6. *
    * If the request header includes an Expect: 100-Continue and the service returns a different value, the method invokation @@ -278,7 +282,6 @@ default void executionFailure(Context.ExecutionFailure context) { *
  • {@link RequestHeaderSent}
  • *
  • {@link RequestBytesSent}
  • *
  • {@link ResponseHeaderReceived}
  • - *
  • {@link AttemptFailureResponseBytesReceived}
  • *
  • {@link ExecutionFailure}
  • * * @@ -423,49 +426,6 @@ public interface ExecutionSuccess extends ResponseBytesReceived { SdkResponse response(); } - /** - * This facilitates capturing and handling an error response returned by service - *

    - * Available context attributes: - *

      - *
    1. {@link AttemptFailureResponseBytesReceived#request()}
    2. - *
    3. {@link AttemptFailureResponseBytesReceived#httpRequest()}
    4. - *
    5. {@link AttemptFailureResponseBytesReceived#uploadProgressSnapshot()}
    6. - *
    7. {@link AttemptFailureResponseBytesReceived#httpResponse()} ()}
    8. - *
    9. {@link AttemptFailureResponseBytesReceived#downloadProgressSnapshot()}
    10. - *
    11. {@link AttemptFailureResponseBytesReceived#exception()}
    12. - *
    - */ - @Immutable - @ThreadSafe - @SdkPublicApi - @SdkPreviewApi - public interface AttemptFailureResponseBytesReceived extends ResponseHeaderReceived { - Throwable exception(); - } - - /** - * The request execution attempt failed. - *

    - * Available context attributes: - *

      - *
    1. {@link AttemptFailure#request()}
    2. - *
    3. {@link AttemptFailure#httpRequest()}
    4. - *
    5. {@link AttemptFailure#uploadProgressSnapshot()}
    6. - *
    7. {@link AttemptFailure#exception()}
    8. - *
    - */ - @Immutable - @ThreadSafe - @SdkPublicApi - @SdkPreviewApi - public interface AttemptFailure extends RequestPrepared { - /** - * The exception associated with the failed request. - */ - Throwable exception(); - } - /** * The request execution failed. *

    @@ -474,6 +434,8 @@ public interface AttemptFailure extends RequestPrepared { *

  • {@link ExecutionFailure#request()}
  • *
  • {@link ExecutionFailure#httpRequest()}
  • *
  • {@link ExecutionFailure#uploadProgressSnapshot()}
  • + *
  • {@link ExecutionFailure#httpResponse()} ()}
  • + *
  • {@link ExecutionFailure#downloadProgressSnapshot()}
  • *
  • {@link ExecutionFailure#exception()}
  • * */ @@ -481,7 +443,7 @@ public interface AttemptFailure extends RequestPrepared { @ThreadSafe @SdkPublicApi @SdkPreviewApi - public interface ExecutionFailure extends RequestPrepared { + public interface ExecutionFailure extends ResponseBytesReceived { /** * The exception associated with the failed request. */ diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkRequestProgress.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkExchangeProgress.java similarity index 86% rename from core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkRequestProgress.java rename to core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkExchangeProgress.java index 837b30535868..2ea1325b242a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkRequestProgress.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/progress/listener/SdkExchangeProgress.java @@ -23,10 +23,11 @@ @Immutable @ThreadSafe @SdkPublicApi -public interface SdkRequestProgress { +public interface SdkExchangeProgress { /** - * Takes a snapshot of the request execution progress + * SdkExchange Progress class stores the Progress Snapshot + * and is used to track request or response progress * represented by an immutable {@link ProgressSnapshot}. */ ProgressSnapshot progressSnapshot(); diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java index 736c43ac476a..72248f46ed54 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.progress.listener.ProgressListener; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.metrics.MetricPublisher; @@ -330,6 +331,34 @@ public void testConfigurationEquals() { assertThat(request1Override).isNotEqualTo(null); } + @Test + public void addProgressListenersList() { + ProgressListener listener1 = mock(ProgressListener.class); + ProgressListener listener2 = mock(ProgressListener.class); + List listProgressListener = Arrays.asList(listener1, listener2); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(listProgressListener); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + assertThat(overrideConfig.progressListeners()).isEqualTo(listProgressListener); + } + + @Test + public void addProgressListeners() { + ProgressListener listener1 = mock(ProgressListener.class); + ProgressListener listener2 = mock(ProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.addProgressListener(listener1); + builder.addProgressListener(listener2); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + assertThat(overrideConfig.progressListeners()).isEqualTo(Arrays.asList(listener1, listener2)); + } + private static class NoOpSigner implements Signer { @Override diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/CaptureProgressListener.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/CaptureProgressListener.java new file mode 100644 index 000000000000..f5e9db0e11ff --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/CaptureProgressListener.java @@ -0,0 +1,117 @@ +/* + * 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.core.internal.progress.listener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import software.amazon.awssdk.core.progress.listener.ProgressListener; + +public class CaptureProgressListener implements ProgressListener { + public Boolean requestPrepared() { + return requestPrepared; + } + + public Boolean requestHeaderSent() { + return requestHeaderSent; + } + + public Boolean responseHeaderReceived() { + return responseHeaderReceived; + } + + public Boolean executionSuccess() { + return executionSuccess; + } + + public List ratioTransferredList() { + return Collections.unmodifiableList(ratioTransferredList); + } + + public CompletableFuture completionFuture() { + return completionFuture; + } + + public Throwable exceptionCaught() { + return exceptionCaught; + } + + private volatile boolean requestPrepared = false; + private volatile boolean requestHeaderSent = false; + private volatile boolean responseHeaderReceived = false; + private volatile boolean executionSuccess = false; + CompletableFuture completionFuture = new CompletableFuture<>(); + + private final List ratioTransferredList = new ArrayList<>(); + private Throwable exceptionCaught; + + @Override + public void requestPrepared(Context.RequestPrepared context) { + requestPrepared = true; + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + + } + + @Override + public void requestHeaderSent(Context.RequestHeaderSent context) { + requestHeaderSent = true; + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + } + + @Override + public void requestBytesSent(Context.RequestBytesSent context) { + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + } + + @Override + public void responseHeaderReceived(Context.ResponseHeaderReceived context) { + responseHeaderReceived = true; + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + } + + @Override + public void responseBytesReceived(Context.ResponseBytesReceived context) { + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + } + + @Override + public void executionSuccess(Context.ExecutionSuccess context) { + context.uploadProgressSnapshot().ratioTransferred().ifPresent(ratioTransferredList::add); + executionSuccess = true; + completionFuture.complete(null); + + } + + @Override + public void executionFailure(Context.ExecutionFailure context) { + exceptionCaught = context.exception(); + completionFuture.completeExceptionally(exceptionCaught); + } + + @Override + public void attemptFailure(Context.ExecutionFailure context) { + exceptionCaught = context.exception(); + completionFuture.completeExceptionally(exceptionCaught); + } + + @Override + public void attemptFailureResponseBytesReceived(Context.ExecutionFailure context) { + exceptionCaught = context.exception(); + completionFuture.completeExceptionally(exceptionCaught); + } +} + diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListenerTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListenerTest.java new file mode 100644 index 000000000000..7c83b4de0fcf --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/LoggingProgressListenerTest.java @@ -0,0 +1,167 @@ +/* + * 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.core.internal.progress.listener; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.internal.progress.ProgressListenerContext; +import software.amazon.awssdk.core.internal.progress.snapshot.DefaultProgressSnapshot; +import software.amazon.awssdk.core.progress.snapshot.ProgressSnapshot; +import software.amazon.awssdk.testutils.LogCaptor; + +public class LoggingProgressListenerTest { + private static final long UPLOAD_SIZE_IN_BYTES = 1024L; + private static final long DOWNLOAD_SIZE_IN_BYTES = 1024L; + private DefaultSdkExchangeProgress requestBodyProgress; + private DefaultSdkExchangeProgress responseBodyProgress; + private ProgressListenerContext context; + private LoggingProgressListener listener; + + @BeforeEach + public void setUp() throws Exception { + ProgressSnapshot uploadSnapshot = DefaultProgressSnapshot.builder() + .transferredBytes(0L) + .totalBytes(UPLOAD_SIZE_IN_BYTES) + .build(); + + ProgressSnapshot downloadSnapshot = DefaultProgressSnapshot.builder() + .transferredBytes(0L) + .totalBytes(DOWNLOAD_SIZE_IN_BYTES) + .build(); + requestBodyProgress = new DefaultSdkExchangeProgress(uploadSnapshot); + responseBodyProgress = new DefaultSdkExchangeProgress(downloadSnapshot); + context = ProgressListenerContext.builder() + .request(mock(NoopTestRequest.class)) + .uploadProgressSnapshot(uploadSnapshot) + .downloadProgressSnapshot(downloadSnapshot) + .build(); + listener = LoggingProgressListener.create(); + } + + @Test + public void defaultListener_successfulTransfer() { + try (LogCaptor logCaptor = LogCaptor.create()) { + invokeSuccessfulLifecycle(); + List events = logCaptor.loggedEvents(); + assertLogged(events, Level.INFO, "Request Prepared..."); + assertLogged(events, Level.INFO, "| | 0.0%"); + assertLogged(events, Level.INFO, "|= | 5.0%"); + assertLogged(events, Level.INFO, "|== | 10.0%"); + assertLogged(events, Level.INFO, "|=== | 15.0%"); + assertLogged(events, Level.INFO, "|==== | 20.0%"); + assertLogged(events, Level.INFO, "|===== | 25.0%"); + assertLogged(events, Level.INFO, "|====== | 30.0%"); + assertLogged(events, Level.INFO, "|======= | 35.0%"); + assertLogged(events, Level.INFO, "|======== | 40.0%"); + assertLogged(events, Level.INFO, "|========= | 45.0%"); + assertLogged(events, Level.INFO, "|========== | 50.0%"); + assertLogged(events, Level.INFO, "|=========== | 55.0%"); + assertLogged(events, Level.INFO, "|============ | 60.0%"); + assertLogged(events, Level.INFO, "|============= | 65.0%"); + assertLogged(events, Level.INFO, "|============== | 70.0%"); + assertLogged(events, Level.INFO, "|=============== | 75.0%"); + assertLogged(events, Level.INFO, "|================ | 80.0%"); + assertLogged(events, Level.INFO, "|================= | 85.0%"); + assertLogged(events, Level.INFO, "|================== | 90.0%"); + assertLogged(events, Level.INFO, "|=================== | 95.0%"); + assertLogged(events, Level.INFO, "|====================| 100.0%"); + assertLogged(events, Level.INFO, "Upload Successful! Starting Download..."); + assertLogged(events, Level.INFO, "| | 0.0%"); + assertLogged(events, Level.INFO, "|= | 5.0%"); + assertLogged(events, Level.INFO, "|== | 10.0%"); + assertLogged(events, Level.INFO, "|=== | 15.0%"); + assertLogged(events, Level.INFO, "|==== | 20.0%"); + assertLogged(events, Level.INFO, "|===== | 25.0%"); + assertLogged(events, Level.INFO, "|====== | 30.0%"); + assertLogged(events, Level.INFO, "|======= | 35.0%"); + assertLogged(events, Level.INFO, "|======== | 40.0%"); + assertLogged(events, Level.INFO, "|========= | 45.0%"); + assertLogged(events, Level.INFO, "|========== | 50.0%"); + assertLogged(events, Level.INFO, "|=========== | 55.0%"); + assertLogged(events, Level.INFO, "|============ | 60.0%"); + assertLogged(events, Level.INFO, "|============= | 65.0%"); + assertLogged(events, Level.INFO, "|============== | 70.0%"); + assertLogged(events, Level.INFO, "|=============== | 75.0%"); + assertLogged(events, Level.INFO, "|================ | 80.0%"); + assertLogged(events, Level.INFO, "|================= | 85.0%"); + assertLogged(events, Level.INFO, "|================== | 90.0%"); + assertLogged(events, Level.INFO, "|=================== | 95.0%"); + assertLogged(events, Level.INFO, "|====================| 100.0%"); + assertLogged(events, Level.INFO, "Execution Successful!"); + assertThat(events).isEmpty(); + } + } + + @Test + public void test_customTicksListener_successfulTransfer() { + try (LogCaptor logCaptor = LogCaptor.create()) { + listener = LoggingProgressListener.create(5); + invokeSuccessfulLifecycle(); + List events = logCaptor.loggedEvents(); + assertLogged(events, Level.INFO, "Request Prepared..."); + assertLogged(events, Level.INFO, "| | 0.0%"); + assertLogged(events, Level.INFO, "|= | 20.0%"); + assertLogged(events, Level.INFO, "|== | 40.0%"); + assertLogged(events, Level.INFO, "|=== | 60.0%"); + assertLogged(events, Level.INFO, "|==== | 80.0%"); + assertLogged(events, Level.INFO, "|=====| 100.0%"); + assertLogged(events, Level.INFO, "Upload Successful! Starting Download..."); + assertLogged(events, Level.INFO, "| | 0.0%"); + assertLogged(events, Level.INFO, "|= | 20.0%"); + assertLogged(events, Level.INFO, "|== | 40.0%"); + assertLogged(events, Level.INFO, "|=== | 60.0%"); + assertLogged(events, Level.INFO, "|==== | 80.0%"); + assertLogged(events, Level.INFO, "|=====| 100.0%"); + assertLogged(events, Level.INFO, "Execution Successful!"); + assertThat(events).isEmpty(); + } + } + + private void invokeSuccessfulLifecycle() { + listener.requestPrepared(context); + + listener.requestHeaderSent(context); + + for (int i = 0; i <= UPLOAD_SIZE_IN_BYTES; i++) { + int bytes = i; + listener.requestBytesSent(context.copy(c -> c.uploadProgressSnapshot( + requestBodyProgress.updateAndGet(p -> p.transferredBytes((long) bytes))))); + } + listener.responseHeaderReceived(context); + for (int i = 0; i <= DOWNLOAD_SIZE_IN_BYTES; i++) { + int bytes = i; + listener.responseBytesReceived(context.copy(c -> c.downloadProgressSnapshot( + responseBodyProgress.updateAndGet(p -> p.transferredBytes((long) bytes))))); + } + + listener.executionSuccess(context.copy(b -> b.downloadProgressSnapshot(responseBodyProgress.progressSnapshot()))); + } + + private static void assertLogged(List events, org.apache.logging.log4j.Level level, String message) { + assertThat(events).withFailMessage("Expecting events to not be empty").isNotEmpty(); + LogEvent event = events.remove(0); + String msg = event.getMessage().getFormattedMessage(); + assertThat(msg).isEqualTo(message); + assertThat(event.getLevel()).isEqualTo(level); + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdaterTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdaterTest.java new file mode 100644 index 000000000000..0cb887561449 --- /dev/null +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/listener/ProgressUpdaterTest.java @@ -0,0 +1,360 @@ +/* + * 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.core.internal.progress.listener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +import java.util.Arrays; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkRequestOverrideConfiguration; +import software.amazon.awssdk.core.http.NoopTestRequest; +import software.amazon.awssdk.core.progress.listener.ProgressListener; +import software.amazon.awssdk.core.protocol.VoidSdkResponse; + +public class ProgressUpdaterTest { + private CaptureProgressListener captureProgressListener; + private static final long BYTES_TRANSFERRED = 5L; + private static final Throwable attemptFailure = new Throwable("AttemptFailureException"); + private static final Throwable executionFailure = new Throwable("ExecutionFailureException"); + private static final Throwable attemptFailureResponseBytesReceived + = new Throwable("AttemptFailureResponseBytesReceivedException"); + + @BeforeEach + void initiate() { + captureProgressListener = new CaptureProgressListener(); + } + + private static Stream contentLength() { + return Stream.of( + Arguments.of(100L), + Arguments.of(200L), + Arguments.of(300L), + Arguments.of(400L), + Arguments.of(500L)); + } + + @Test + public void requestPrepared_transferredBytes_equals_zero() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.requestPrepared(); + + assertEquals(0.0, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + assertTrue(captureProgressListener.requestPrepared()); + assertFalse(captureProgressListener.requestHeaderSent()); + assertFalse(captureProgressListener.responseHeaderReceived()); + Mockito.verify(mockListener, never()).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(1)).requestPrepared(ArgumentMatchers.any(ProgressListener.Context.RequestPrepared.class)); + + + } + + @Test + public void requestHeaderSent_transferredBytes_equals_zero() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.requestHeaderSent(); + + assertEquals(0.0, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + assertFalse(captureProgressListener.requestPrepared()); + assertTrue(captureProgressListener.requestHeaderSent()); + assertFalse(captureProgressListener.responseHeaderReceived()); + Mockito.verify(mockListener, never()).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(1)).requestHeaderSent(ArgumentMatchers.any(ProgressListener.Context.RequestHeaderSent.class)); + + } + + @Test + public void requestBytesSent_transferredBytes() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.incrementBytesSent(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + progressUpdater.incrementBytesSent(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED + BYTES_TRANSFERRED, + progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + Mockito.verify(mockListener, never()).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(2)).requestBytesSent(ArgumentMatchers.any(ProgressListener.Context.RequestBytesSent.class)); + + } + + @Test + public void validate_resetBytesSent() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.incrementBytesSent(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + progressUpdater.resetBytesSent(); + assertEquals(0, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + } + + @Test + public void validate_resetBytesReceived() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.incrementBytesReceived(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED, progressUpdater.responseBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + progressUpdater.resetBytesReceived(); + assertEquals(0, progressUpdater.responseBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + } + + @ParameterizedTest + @MethodSource("contentLength") + public void ratioTransferred_upload_transferredBytes(long contentLength) { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, contentLength); + progressUpdater.incrementBytesSent(BYTES_TRANSFERRED); + assertEquals((double) BYTES_TRANSFERRED / contentLength, + progressUpdater.requestBodyProgress().progressSnapshot().ratioTransferred().getAsDouble(), 0.0); + + } + + @Test + public void responseHeaderReceived_transferredBytes_equals_zero() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.responseHeaderReceived(); + + assertEquals(0.0, progressUpdater.requestBodyProgress().progressSnapshot().transferredBytes(), 0.0); + assertFalse(captureProgressListener.requestPrepared()); + assertFalse(captureProgressListener.requestHeaderSent()); + assertTrue(captureProgressListener.responseHeaderReceived()); + Mockito.verify(mockListener, never()).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(1)).responseHeaderReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseHeaderReceived.class)); + + } + + @Test + public void executionSuccess_transferredBytes_valid() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.incrementBytesReceived(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED, progressUpdater.responseBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + progressUpdater.incrementBytesReceived(BYTES_TRANSFERRED); + assertEquals(BYTES_TRANSFERRED + BYTES_TRANSFERRED, + progressUpdater.responseBodyProgress().progressSnapshot().transferredBytes(), 0.0); + + progressUpdater.executionSuccess(VoidSdkResponse.builder().sdkHttpResponse(null).build()); + Mockito.verify(mockListener, never()).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, never()).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(2)).responseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseBytesReceived.class)); + Mockito.verify(mockListener, times(1)).executionSuccess(ArgumentMatchers.any(ProgressListener.Context.ExecutionSuccess.class)); + } + + @Test + public void attemptFailureResponseBytesReceived() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.requestPrepared(); + progressUpdater.responseHeaderReceived(); + progressUpdater.attemptFailureResponseBytesReceived(attemptFailureResponseBytesReceived); + + Mockito.verify(mockListener, times(1)).requestPrepared(ArgumentMatchers.any(ProgressListener.Context.RequestPrepared.class)); + Mockito.verify(mockListener, times(1)).responseHeaderReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseHeaderReceived.class)); + Mockito.verify(mockListener, times(1)).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(0)).responseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseBytesReceived.class)); + Mockito.verify(mockListener, times(0)).executionSuccess(ArgumentMatchers.any(ProgressListener.Context.ExecutionSuccess.class)); + + Assertions.assertEquals(captureProgressListener.exceptionCaught().getMessage(), attemptFailureResponseBytesReceived.getMessage()); + } + + @Test + public void attemptFailure() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.requestPrepared(); + progressUpdater.attemptFailure(attemptFailure); + + Mockito.verify(mockListener, times(1)).requestPrepared(ArgumentMatchers.any(ProgressListener.Context.RequestPrepared.class)); + Mockito.verify(mockListener, times(0)).responseHeaderReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseHeaderReceived.class)); + Mockito.verify(mockListener, times(0)).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(0)).responseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseBytesReceived.class)); + Mockito.verify(mockListener, times(0)).executionSuccess(ArgumentMatchers.any(ProgressListener.Context.ExecutionSuccess.class)); + Mockito.verify(mockListener, times(1)).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(0)).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + + Assertions.assertEquals(captureProgressListener.exceptionCaught().getMessage(), attemptFailure.getMessage()); + } + + @Test + public void executionFailure() { + + CaptureProgressListener mockListener = Mockito.mock(CaptureProgressListener.class); + + SdkRequestOverrideConfiguration.Builder builder = SdkRequestOverrideConfiguration.builder(); + builder.progressListeners(Arrays.asList(mockListener, captureProgressListener)); + + SdkRequestOverrideConfiguration overrideConfig = builder.build(); + + SdkRequest sdkRequest = NoopTestRequest.builder() + .overrideConfiguration(overrideConfig) + .build(); + + ProgressUpdater progressUpdater = new ProgressUpdater(sdkRequest, null); + progressUpdater.requestPrepared(); + progressUpdater.executionFailure(executionFailure); + + + Mockito.verify(mockListener, times(1)).requestPrepared(ArgumentMatchers.any(ProgressListener.Context.RequestPrepared.class)); + Mockito.verify(mockListener, times(0)).responseHeaderReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseHeaderReceived.class)); + Mockito.verify(mockListener, times(0)).attemptFailureResponseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(0)).responseBytesReceived(ArgumentMatchers.any(ProgressListener.Context.ResponseBytesReceived.class)); + Mockito.verify(mockListener, times(0)).executionSuccess(ArgumentMatchers.any(ProgressListener.Context.ExecutionSuccess.class)); + Mockito.verify(mockListener, times(0)).attemptFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + Mockito.verify(mockListener, times(1)).executionFailure(ArgumentMatchers.any(ProgressListener.Context.ExecutionFailure.class)); + + Assertions.assertEquals(captureProgressListener.exceptionCaught().getMessage(), executionFailure.getMessage()); + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshotTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshotTest.java index 49acebb1da4f..b2d125f656d1 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshotTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/progress/snapshot/DefaultProgressSnapshotTest.java @@ -39,10 +39,6 @@ private static Stream getArgumentsForInvalidParameterValidationTests( .transferredBytes(2L) .totalBytes(1L), new IllegalArgumentException()), - Arguments.of("startTime must not be null.", - DefaultProgressSnapshot.builder() - .transferredBytes(2L), - new NullPointerException()), Arguments.of("transferredBytes must not be negative", DefaultProgressSnapshot.builder() .transferredBytes(-2L),