Package com.google.appengine.tools.cloudstorage

Source Code of com.google.appengine.tools.cloudstorage.RetryHelperTest$FakeTicker

/*
* Copyright 2012 Google Inc. 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.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 com.google.appengine.tools.cloudstorage;

import static java.util.concurrent.Executors.callable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
* Tests for Retry helper.
*
*/
@RunWith(JUnit4.class)
public class RetryHelperTest {

  @Test
  public void testTriesWithExceptionHandling() {
    assertNull(RetryHelper.getContext());
    RetryParams params =
        new RetryParams.Builder().initialRetryDelayMillis(0).retryMaxAttempts(3).build();
    ExceptionHandler handler = new ExceptionHandler.Builder()
        .retryOn(IOException.class).abortOn(RuntimeException.class).build();
    final AtomicInteger count = new AtomicInteger(3);
    try {
      RetryHelper.runWithRetries(new Callable<Void>() {
        @Override public Void call() throws IOException, NullPointerException {
          if (count.decrementAndGet() == 2) {
            assertEquals(1, RetryHelper.getContext().getAttemptNumber());
            throw new IOException("should be retried");
          }
          assertEquals(2, RetryHelper.getContext().getAttemptNumber());
          throw new NullPointerException("Boo!");
        }
      }, params, handler);
      fail("Exception should have been thrown");
    } catch (NonRetriableException ex) {
      assertEquals("Boo!", ex.getCause().getMessage());
      assertEquals(1, count.intValue());
    }
    assertNull(RetryHelper.getContext());

    @SuppressWarnings("serial") class E1 extends Exception {}
    @SuppressWarnings("serial") class E2 extends E1 {}
    @SuppressWarnings("serial") class E3 extends E1 {}
    @SuppressWarnings("serial") class E4 extends E2 {}

    params = new RetryParams.Builder().initialRetryDelayMillis(0).retryMaxAttempts(5).build();
    handler = new ExceptionHandler.Builder().retryOn(E1.class, E4.class).abortOn(E3.class).build();
    final Iterator<? extends E1> exceptions =
        Arrays.asList(new E1(), new E2(), new E4(), new E3()).iterator();
    try {
      RetryHelper.runWithRetries(new Callable<Void>() {
        @Override public Void call() throws E1 {
          E1 exception = exceptions.next();
          throw exception;
        }
      }, params, handler);
      fail("Exception should have been thrown");
    } catch (NonRetriableException ex) {
      assertTrue(ex.getCause() instanceof E3);
    }
    assertNull(RetryHelper.getContext());
  }

  @Test
  public void testTriesAtLeastMinTimes() {
    RetryParams params = new RetryParams.Builder().initialRetryDelayMillis(0)
        .totalRetryPeriodMillis(60000)
        .retryMinAttempts(5)
        .retryMaxAttempts(10)
        .build();
    final int timesToFail = 7;
    assertNull(RetryHelper.getContext());
    int attempted = RetryHelper.runWithRetries(new Callable<Integer>() {
      int timesCalled = 0;
      @Override public Integer call() throws IOException {
        timesCalled++;
        assertEquals(timesCalled, RetryHelper.getContext().getAttemptNumber());
        assertEquals(10, RetryHelper.getContext().getRetryParams().getRetryMaxAttempts());
        if (timesCalled <= timesToFail) {
          throw new IOException();
        } else {
          return timesCalled;
        }
      }
    }, params, ExceptionHandler.getDefaultInstance());
    assertEquals(timesToFail + 1, attempted);
    assertNull(RetryHelper.getContext());
  }

  @Test
  public void testTriesNoMoreThanMaxTimes() {
    final int maxAttempts = 10;
    RetryParams params = new RetryParams.Builder().initialRetryDelayMillis(0)
        .totalRetryPeriodMillis(60000)
        .retryMinAttempts(0)
        .retryMaxAttempts(maxAttempts)
        .build();
    final AtomicInteger timesCalled = new AtomicInteger(0);
    try {
      RetryHelper.runWithRetries(callable(new Runnable() {
        @Override public void run() {
          if (timesCalled.incrementAndGet() <= maxAttempts) {
            throw new RuntimeException();
          }
          fail("Body was executed too many times: " + timesCalled.get());
        }
      }), params, new ExceptionHandler.Builder().retryOn(RuntimeException.class).build());
      fail("Should not have succeeded, expected all attempts to fail and give up.");
    } catch (RetriesExhaustedException expected) {
      assertEquals(maxAttempts, timesCalled.get());
    }
  }

  private class FakeTicker extends Ticker {
    private final AtomicLong nanos = new AtomicLong();

    /** Advances the ticker value by {@code time} in {@code timeUnit}. */
    FakeTicker advance(long time, TimeUnit timeUnit) {
      return advance(timeUnit.toNanos(time));
    }

    /** Advances the ticker value by {@code nanoseconds}. */
    FakeTicker advance(long nanoseconds) {
      nanos.addAndGet(nanoseconds);
      return this;
    }

    @Override
    public long read() {
      return nanos.get();
    }
  }

  @Test
  public void testTriesNoMoreLongerThanTotalRetryPeriod() {
    final FakeTicker ticker = new FakeTicker();
    Stopwatch stopwatch = Stopwatch.createUnstarted(ticker);
    RetryParams params = new RetryParams.Builder().initialRetryDelayMillis(0)
        .totalRetryPeriodMillis(999)
        .retryMinAttempts(5)
        .retryMaxAttempts(10)
        .build();
    ExceptionHandler handler =
        new ExceptionHandler.Builder().retryOn(RuntimeException.class).build();
    final int sleepOnAttempt = 8;
    final AtomicInteger timesCalled = new AtomicInteger(0);
    try {
      RetryHelper.runWithRetries(callable(new Runnable() {
        @Override public void run() {
          timesCalled.incrementAndGet();
          if (timesCalled.get() == sleepOnAttempt) {
            ticker.advance(1000, TimeUnit.MILLISECONDS);
          }
          throw new RuntimeException();
        }
      }), params, handler, stopwatch);
      fail();
    } catch (RetriesExhaustedException e) {
      assertEquals(sleepOnAttempt, timesCalled.get());
    }
  }

  @Test
  public void testBackoffIsExponential() {
    RetryParams params = new RetryParams.Builder()
        .initialRetryDelayMillis(10)
        .maxRetryDelayMillis(10_000_000)
        .retryDelayBackoffFactor(2)
        .totalRetryPeriodMillis(60_000)
        .retryMinAttempts(0)
        .retryMaxAttempts(100)
        .build();
    long sleepDuration = RetryHelper.getSleepDuration(params, 1);
    assertTrue("" + sleepDuration, sleepDuration < 13 && sleepDuration >= 7);
    sleepDuration = RetryHelper.getSleepDuration(params, 2);
    assertTrue("" + sleepDuration, sleepDuration < 25 && sleepDuration >= 15);
    sleepDuration = RetryHelper.getSleepDuration(params, 3);
    assertTrue("" + sleepDuration, sleepDuration < 50 && sleepDuration >= 30);
    sleepDuration = RetryHelper.getSleepDuration(params, 4);
    assertTrue("" + sleepDuration, sleepDuration < 100 && sleepDuration >= 60);
    sleepDuration = RetryHelper.getSleepDuration(params, 5);
    assertTrue("" + sleepDuration, sleepDuration < 200 && sleepDuration >= 120);
    sleepDuration = RetryHelper.getSleepDuration(params, 6);
    assertTrue("" + sleepDuration, sleepDuration < 400 && sleepDuration >= 240);
    sleepDuration = RetryHelper.getSleepDuration(params, 7);
    assertTrue("" + sleepDuration, sleepDuration < 800 && sleepDuration >= 480);
    sleepDuration = RetryHelper.getSleepDuration(params, 8);
    assertTrue("" + sleepDuration, sleepDuration < 1600 && sleepDuration >= 960);
    sleepDuration = RetryHelper.getSleepDuration(params, 9);
    assertTrue("" + sleepDuration, sleepDuration < 3200 && sleepDuration >= 1920);
    sleepDuration = RetryHelper.getSleepDuration(params, 10);
    assertTrue("" + sleepDuration, sleepDuration < 6400 && sleepDuration >= 3840);
    sleepDuration = RetryHelper.getSleepDuration(params, 11);
    assertTrue("" + sleepDuration, sleepDuration < 12800 && sleepDuration >= 7680);
    sleepDuration = RetryHelper.getSleepDuration(params, 12);
    assertTrue("" + sleepDuration, sleepDuration < 25600 && sleepDuration >= 15360);
  }

  @Test
  public void testNestedUsage() {
    assertEquals((1 + 3) * 2, invokeNested(3, 2));
  }

  private int invokeNested(final int level, final int retries) {
    if (level < 0) {
      return 0;
    }
    return RetryHelper.runWithRetries(new Callable<Integer>() {
      @Override
      public Integer call() throws IOException {
        if (RetryHelper.getContext().getAttemptNumber() < retries) {
          throw new IOException();
        }
        assertEquals(retries, RetryHelper.getContext().getAttemptNumber());
        return invokeNested(level - 1, retries) + RetryHelper.getContext().getAttemptNumber();
      }
    });
  }

}
TOP

Related Classes of com.google.appengine.tools.cloudstorage.RetryHelperTest$FakeTicker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.