All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.testutils.Waiter Maven / Gradle / Ivy

Go to download

The AWS SDK for Java - Test Utils module holds the all the utilities that are used by the tests.

There is a newer version: 2.29.17
Show newest version
/*
 * 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.testutils;

import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.function.Predicate;
import java.util.function.Supplier;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.Validate;

/**
 * This retries a particular function multiple times until it returns an expected result (or fails with an exception). Certain
 * expected exception types can be ignored.
 */
public final class Waiter {
    private static final Logger log = Logger.loggerFor(Waiter.class);

    private final Supplier thingToTry;
    private Predicate whenToStop = t -> true;
    private Predicate whenToFail = t -> false;
    private Set> whatExceptionsToStopOn = Collections.emptySet();
    private Set> whatExceptionsToIgnore = Collections.emptySet();

    /**
     * @see #run(Supplier)
     */
    private Waiter(Supplier thingToTry) {
        Validate.paramNotNull(thingToTry, "thingToTry");
        this.thingToTry = thingToTry;
    }

    /**
     * Create a waiter that attempts executing the provided function until the condition set with {@link #until(Predicate)} is
     * met or until it throws an exception. Expected exception types can be ignored with {@link #ignoringException(Class[])}.
     */
    public static  Waiter run(Supplier thingToTry) {
        return new Waiter<>(thingToTry);
    }

    /**
     * Define the condition on the response under which the thing we are trying is complete.
     *
     * If this isn't set, it will always be true. ie. if the function call succeeds, we stop waiting.
     */
    public Waiter until(Predicate whenToStop) {
        this.whenToStop = whenToStop;
        return this;
    }

    /**
     * Define the condition on the response under which the thing we are trying has already failed and further
     * attempts are pointless.
     *
     * If this isn't set, it will always be false.
     */
    public Waiter failOn(Predicate whenToFail) {
        this.whenToFail = whenToFail;
        return this;
    }

    /**
     * Define the condition on an exception thrown under which the thing we are trying is complete.
     *
     * If this isn't set, it will always be false. ie. never stop on any particular exception.
     */
    @SafeVarargs
    public final Waiter untilException(Class... whenToStopOnException) {
        this.whatExceptionsToStopOn = new HashSet<>(Arrays.asList(whenToStopOnException));
        return this;
    }

    /**
     * Define the exception types that should be ignored if the thing we are trying throws them.
     */
    @SafeVarargs
    public final Waiter ignoringException(Class... whatToIgnore) {
        this.whatExceptionsToIgnore = new HashSet<>(Arrays.asList(whatToIgnore));
        return this;
    }

    /**
     * Execute the function, returning true if the thing we're trying does not succeed after 30 seconds.
     */
    public boolean orReturnFalse() {
        try {
            orFail();
            return true;
        } catch (AssertionError e) {
            return false;
        }
    }

    /**
     * Execute the function, throwing an assertion error if the thing we're trying does not succeed after 30 seconds.
     */
    public T orFail() {
        return orFailAfter(Duration.ofMinutes(1));
    }

    /**
     * Execute the function, throwing an assertion error if the thing we're trying does not succeed after the provided duration.
     */
    public T orFailAfter(Duration howLongToTry) {
        Validate.paramNotNull(howLongToTry, "howLongToTry");

        Instant start = Instant.now();
        int attempt = 0;

        while (Duration.between(start, Instant.now()).compareTo(howLongToTry) < 0) {
            ++attempt;
            try {
                if (attempt > 1) {
                    wait(attempt);
                }

                T result = thingToTry.get();
                if (whenToStop.test(result)) {
                    log.debug(() -> "Got expected response: " + result);
                    return result;
                } else if (whenToFail.test(result)) {
                    throw new AssertionError("Received a response that matched the failOn predicate: " + result);
                }
                int unsuccessfulAttempt = attempt;
                log.info(() -> "Attempt " + unsuccessfulAttempt + " failed predicate.");
            } catch (RuntimeException e) {
                Throwable t = e instanceof CompletionException ? e.getCause() : e;

                if (whatExceptionsToStopOn.contains(t.getClass())) {
                    log.info(() -> "Got expected exception: " + t.getClass().getSimpleName());
                    return null;
                }

                if (whatExceptionsToIgnore.contains(t.getClass())) {
                    int unsuccessfulAttempt = attempt;
                    log.info(() -> "Attempt " + unsuccessfulAttempt +
                                   " failed with an expected exception (" + t.getClass() + ")");
                } else {
                    throw e;
                }
            }
        }

        throw new AssertionError("Condition was not met after " + attempt + " attempts (" +
                                 Duration.between(start, Instant.now()).getSeconds() + " seconds)");
    }

    private void wait(int attempt) {
        int howLongToWaitMs = 250 << Math.min(attempt - 1, 4); // Max = 250 * 2^4 = 4_000.

        try {
            Thread.sleep(howLongToWaitMs);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new AssertionError(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy