/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2015 ForgeRock AS. */ package org.opends.server.util; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.forgerock.util.Reject; /** * Timer useful for testing: it helps to write loops that repeatedly runs code until some condition * is met. */ public interface TestTimer { /** * Constant that can be used at the end of {@code Callable.call()} to better explicit this * is the end of the method. */ Void END_RUN = null; /** * Repeatedly call the supplied callable (respecting a sleep interval) until: * * If the current timer times out, then it will: * *

* Note: The test code in the callable can be written as any test code outside a callable. In * particular, asserts can and should be used inside the {@link Callable#call()}. * * @param callable * the callable to repeat until success * @param * The return type of the callable * @return the value returned by the callable (may be {@code null}), or {@code null} if the timer * times out * @throws Exception * The exception thrown by the provided callable * @throws InterruptedException * If the thread is interrupted while sleeping */ R repeatUntilSuccess(Callable callable) throws Exception, InterruptedException; /** Builder for a {@link TestTimer}. */ public static final class Builder { private long maxSleepTimeInMillis; private long sleepTimes; /** * Configures the maximum sleep duration. * * @param time * the duration * @param unit * the time unit for the duration * @return this builder */ public Builder maxSleep(long time, TimeUnit unit) { Reject.ifFalse(time > 0, "time must be positive"); this.maxSleepTimeInMillis = unit.toMillis(time); return this; } /** * Configures the duration for sleep times. * * @param time * the duration * @param unit * the time unit for the duration * @return this builder */ public Builder sleepTimes(long time, TimeUnit unit) { Reject.ifFalse(time > 0, "time must be positive"); this.sleepTimes = unit.toMillis(time); return this; } /** * Creates a new timer and start it. * * @return a new timer */ public TestTimer toTimer() { return new SteppingTimer(this); } } /** A {@link TestTimer} that sleeps in steps and sleeps at maximum {@code nbSteps * sleepTimes}. */ public static class SteppingTimer implements TestTimer { private final long sleepTime; private final long totalNbSteps; private long nbStepsRemaining; private SteppingTimer(Builder builder) { this.sleepTime = builder.sleepTimes; this.totalNbSteps = sleepTime > 0 ? builder.maxSleepTimeInMillis / sleepTime : 0; this.nbStepsRemaining = totalNbSteps; } /** * Returns whether the timer has reached the timeout. This method may block by sleeping. * * @return {@code true} if the timer has reached the timeout, {@code false} otherwise * @throws InterruptedException if the thread has been interrupted */ private boolean hasTimedOut() throws InterruptedException { final boolean done = hasTimedOutNoSleep(); if (!done) { Thread.sleep(sleepTime); nbStepsRemaining--; } return done; } /** * Returns whether the timer has reached the timeout, without sleep. * * @return {@code true} if the timer has reached the timeout, {@code false} otherwise */ private boolean hasTimedOutNoSleep() { return nbStepsRemaining <= 0; } @Override public R repeatUntilSuccess(Callable callable) throws Exception, InterruptedException { do { try { return callable.call(); } catch (AssertionError e) { if (hasTimedOutNoSleep()) { throw e; } } } while (!hasTimedOut()); return null; } @Override public String toString() { return totalNbSteps * sleepTime + " ms max sleep time" + " (" + totalNbSteps + " steps x " + sleepTime + " ms)" + ", remaining = " + nbStepsRemaining + " steps"; } } }