| /* |
| * Copyright 2016, Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package io.grpc; |
| |
| import static com.google.common.truth.Truth.assertAbout; |
| import static io.grpc.testing.DeadlineSubject.deadline; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Matchers.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.verify; |
| |
| import com.google.common.truth.Truth; |
| import java.util.Arrays; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| import org.mockito.ArgumentCaptor; |
| |
| /** |
| * Tests for {@link Context}. |
| */ |
| @RunWith(Parameterized.class) |
| public class DeadlineTest { |
| /** Ticker epochs to vary testing. */ |
| @Parameters |
| public static Iterable<Object[]> data() { |
| return Arrays.asList(new Object[][] { |
| // MAX_VALUE / 2 is important because the signs are generally the same for past and future |
| // deadlines. |
| {Long.MAX_VALUE / 2}, {0}, {Long.MAX_VALUE}, {Long.MIN_VALUE} |
| }); |
| } |
| |
| private FakeTicker ticker = new FakeTicker(); |
| |
| public DeadlineTest(long epoch) { |
| ticker.reset(epoch); |
| } |
| |
| @Test |
| public void defaultTickerIsSystemTicker() { |
| Deadline d = Deadline.after(0, TimeUnit.SECONDS); |
| ticker.reset(System.nanoTime()); |
| Deadline reference = Deadline.after(0, TimeUnit.SECONDS, ticker); |
| // Allow inaccuracy to account for system time advancing during test. |
| assertAbout(deadline()).that(d).isWithin(1, TimeUnit.SECONDS).of(reference); |
| } |
| |
| @Test |
| public void timeCanOverflow() { |
| ticker.reset(Long.MAX_VALUE); |
| Deadline d = Deadline.after(10, TimeUnit.DAYS, ticker); |
| assertEquals(10, d.timeRemaining(TimeUnit.DAYS)); |
| assertTrue(Deadline.after(0, TimeUnit.DAYS, ticker).isBefore(d)); |
| assertFalse(d.isExpired()); |
| |
| ticker.increment(10, TimeUnit.DAYS); |
| assertTrue(d.isExpired()); |
| } |
| |
| @Test |
| public void timeCanUnderflow() { |
| ticker.reset(Long.MIN_VALUE); |
| Deadline d = Deadline.after(-10, TimeUnit.DAYS, ticker); |
| assertEquals(-10, d.timeRemaining(TimeUnit.DAYS)); |
| assertTrue(d.isBefore(Deadline.after(0, TimeUnit.DAYS, ticker))); |
| assertTrue(d.isExpired()); |
| } |
| |
| @Test |
| public void deadlineClamps() { |
| Deadline d = Deadline.after(-300 * 365, TimeUnit.DAYS, ticker); |
| Deadline d2 = Deadline.after(300 * 365, TimeUnit.DAYS, ticker); |
| assertTrue(d.isBefore(d2)); |
| |
| Deadline d3 = Deadline.after(-200 * 365, TimeUnit.DAYS, ticker); |
| // d and d3 are equal |
| assertFalse(d.isBefore(d3)); |
| assertFalse(d3.isBefore(d)); |
| } |
| |
| @Test |
| public void immediateDeadlineIsExpired() { |
| Deadline deadline = Deadline.after(0, TimeUnit.SECONDS, ticker); |
| assertTrue(deadline.isExpired()); |
| } |
| |
| @Test |
| public void shortDeadlineEventuallyExpires() throws Exception { |
| Deadline d = Deadline.after(100, TimeUnit.MILLISECONDS, ticker); |
| assertTrue(d.timeRemaining(TimeUnit.NANOSECONDS) > 0); |
| assertFalse(d.isExpired()); |
| ticker.increment(101, TimeUnit.MILLISECONDS); |
| |
| assertTrue(d.isExpired()); |
| assertEquals(-1, d.timeRemaining(TimeUnit.MILLISECONDS)); |
| } |
| |
| @Test |
| public void deadlineMatchesLongValue() { |
| assertEquals(10, Deadline.after(10, TimeUnit.MINUTES, ticker).timeRemaining(TimeUnit.MINUTES)); |
| } |
| |
| @Test |
| public void pastDeadlineIsExpired() { |
| Deadline d = Deadline.after(-1, TimeUnit.SECONDS, ticker); |
| assertTrue(d.isExpired()); |
| assertEquals(-1000, d.timeRemaining(TimeUnit.MILLISECONDS)); |
| } |
| |
| @Test |
| public void deadlineDoesNotOverflowOrUnderflow() { |
| Deadline after = Deadline.after(Long.MAX_VALUE, TimeUnit.NANOSECONDS, ticker); |
| assertFalse(after.isExpired()); |
| |
| Deadline before = Deadline.after(-Long.MAX_VALUE, TimeUnit.NANOSECONDS, ticker); |
| assertTrue(before.isExpired()); |
| |
| assertTrue(before.isBefore(after)); |
| } |
| |
| @Test |
| public void beforeExpiredDeadlineIsExpired() { |
| Deadline base = Deadline.after(0, TimeUnit.SECONDS, ticker); |
| assertTrue(base.isExpired()); |
| assertTrue(base.offset(-1, TimeUnit.SECONDS).isExpired()); |
| } |
| |
| @Test |
| public void beforeNotExpiredDeadlineMayBeExpired() { |
| Deadline base = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| assertFalse(base.isExpired()); |
| assertFalse(base.offset(-1, TimeUnit.SECONDS).isExpired()); |
| assertTrue(base.offset(-11, TimeUnit.SECONDS).isExpired()); |
| } |
| |
| @Test |
| public void afterExpiredDeadlineMayBeExpired() { |
| Deadline base = Deadline.after(-10, TimeUnit.SECONDS, ticker); |
| assertTrue(base.isExpired()); |
| assertTrue(base.offset(1, TimeUnit.SECONDS).isExpired()); |
| assertFalse(base.offset(11, TimeUnit.SECONDS).isExpired()); |
| } |
| |
| @Test |
| public void zeroOffsetIsSameDeadline() { |
| Deadline base = Deadline.after(0, TimeUnit.SECONDS, ticker); |
| assertSame(base, base.offset(0, TimeUnit.SECONDS)); |
| } |
| |
| @Test |
| public void runOnEventualExpirationIsExecuted() throws Exception { |
| Deadline base = Deadline.after(50, TimeUnit.MICROSECONDS, ticker); |
| ScheduledExecutorService mockScheduler = mock(ScheduledExecutorService.class); |
| final AtomicBoolean executed = new AtomicBoolean(); |
| Future<?> unused = base.runOnExpiration( |
| new Runnable() { |
| @Override |
| public void run() { |
| executed.set(true); |
| } |
| }, mockScheduler); |
| assertFalse(executed.get()); |
| ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); |
| verify(mockScheduler).schedule(runnableCaptor.capture(), eq(50000L), eq(TimeUnit.NANOSECONDS)); |
| runnableCaptor.getValue().run(); |
| assertTrue(executed.get()); |
| } |
| |
| @Test |
| public void runOnAlreadyExpiredIsExecutedOnExecutor() throws Exception { |
| Deadline base = Deadline.after(0, TimeUnit.MICROSECONDS, ticker); |
| ScheduledExecutorService mockScheduler = mock(ScheduledExecutorService.class); |
| final AtomicBoolean executed = new AtomicBoolean(); |
| Future<?> unused = base.runOnExpiration( |
| new Runnable() { |
| @Override |
| public void run() { |
| executed.set(true); |
| } |
| }, mockScheduler); |
| assertFalse(executed.get()); |
| ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); |
| verify(mockScheduler).schedule(runnableCaptor.capture(), eq(0L), eq(TimeUnit.NANOSECONDS)); |
| runnableCaptor.getValue().run(); |
| assertTrue(executed.get()); |
| } |
| |
| @Test |
| public void toString_exact() { |
| Deadline d = Deadline.after(0, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("0 ns from now", d.toString()); |
| } |
| |
| @Test |
| public void toString_after() { |
| Deadline d = Deadline.after(-1, TimeUnit.MINUTES, ticker); |
| assertEquals("-60000000000 ns from now", d.toString()); |
| } |
| |
| @Test |
| public void compareTo_greater() { |
| Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| ticker.increment(1, TimeUnit.NANOSECONDS); |
| Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| Truth.assertThat(d2).isGreaterThan(d1); |
| } |
| |
| @Test |
| public void compareTo_less() { |
| Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| ticker.increment(1, TimeUnit.NANOSECONDS); |
| Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| Truth.assertThat(d1).isLessThan(d2); |
| } |
| |
| @Test |
| public void compareTo_same() { |
| Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| Truth.assertThat(d1).isEquivalentAccordingToCompareTo(d2); |
| } |
| |
| @Test |
| public void toString_before() { |
| Deadline d = Deadline.after(12, TimeUnit.MICROSECONDS, ticker); |
| assertEquals("12000 ns from now", d.toString()); |
| } |
| |
| private static class FakeTicker extends Deadline.Ticker { |
| private long time; |
| |
| @Override |
| public long read() { |
| return time; |
| } |
| |
| public void reset(long time) { |
| this.time = time; |
| } |
| |
| public void increment(long period, TimeUnit unit) { |
| if (period < 0) { |
| throw new IllegalArgumentException(); |
| } |
| this.time += unit.toNanos(period); |
| } |
| } |
| } |