/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * 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<Void>.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: * <ul> * <li>it returns,</li> * <li>it throws an exception other than {@link AssertionError},</li> * <li>the current timer times out.</li> * </ul> * If the current timer times out, then it will: * <ul> * <li>either rethrow an {@link AssertionError} thrown by the callable,</li> * <li>or return {@code null}.</li> * </ul> * <p> * 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 <R> * 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> R repeatUntilSuccess(Callable<R> 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> R repeatUntilSuccess(Callable<R> 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"; } } }