| /* |
| * Copyright 2016 The gRPC Authors |
| * |
| * 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 io.grpc; |
| |
| import static com.google.common.truth.Truth.assertAbout; |
| import static com.google.common.truth.Truth.assertThat; |
| 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.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.verify; |
| |
| import com.google.common.testing.EqualsTester; |
| 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 minimum() { |
| Deadline d1 = Deadline.after(1, TimeUnit.MINUTES, ticker); |
| Deadline d2 = Deadline.after(2, TimeUnit.MINUTES, ticker); |
| Deadline d3 = Deadline.after(3, TimeUnit.MINUTES, ticker); |
| |
| assertThat(d1.minimum(d2)).isSameInstanceAs(d1); |
| assertThat(d2.minimum(d1)).isSameInstanceAs(d1); |
| assertThat(d3.minimum(d2)).isSameInstanceAs(d2); |
| assertThat(d2.minimum(d3)).isSameInstanceAs(d2); |
| } |
| |
| @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_systemTickerNotShown() { |
| Deadline d = Deadline.after(0, TimeUnit.MILLISECONDS); |
| assertThat(d.toString()).endsWith("s from now"); |
| } |
| |
| @Test |
| public void toString_exact() { |
| Deadline d = Deadline.after(0, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("0s from now (ticker=FAKE_TICKER)", d.toString()); |
| } |
| |
| @Test |
| public void toString_after() { |
| Deadline d; |
| |
| d = Deadline.after(-1, TimeUnit.MINUTES, ticker); |
| assertEquals("-60s from now (ticker=FAKE_TICKER)", d.toString()); |
| d = Deadline.after(-1, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("-0.001000000s from now (ticker=FAKE_TICKER)", d.toString()); |
| d = Deadline.after(-500, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("-0.500000000s from now (ticker=FAKE_TICKER)", d.toString()); |
| d = Deadline.after(-1000, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("-1s from now (ticker=FAKE_TICKER)", d.toString()); |
| d = Deadline.after(-1500, TimeUnit.MILLISECONDS, ticker); |
| assertEquals("-1.500000000s from now (ticker=FAKE_TICKER)", d.toString()); |
| d = Deadline.after(-1023456789, TimeUnit.NANOSECONDS, ticker); |
| assertEquals("-1.023456789s from now (ticker=FAKE_TICKER)", 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 tickersDontMatch() { |
| Deadline d1 = Deadline.after(10, TimeUnit.SECONDS); |
| Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker); |
| boolean success = false; |
| try { |
| d1.compareTo(d2); |
| success = true; |
| } catch (AssertionError e) { |
| // Expected |
| } |
| assertFalse(success); |
| |
| try { |
| d1.minimum(d2); |
| success = true; |
| } catch (AssertionError e) { |
| // Expected |
| } |
| assertFalse(success); |
| |
| try { |
| d1.isBefore(d2); |
| success = true; |
| } catch (AssertionError e) { |
| // Expected |
| } |
| assertFalse(success); |
| } |
| |
| @Test |
| public void toString_before() { |
| Deadline d = Deadline.after(12, TimeUnit.MICROSECONDS, ticker); |
| assertEquals("0.000012000s from now (ticker=FAKE_TICKER)", d.toString()); |
| } |
| |
| @Test |
| public void equality() { |
| final Deadline d1 = Deadline.after(12, TimeUnit.MICROSECONDS, ticker); |
| final Deadline d2 = Deadline.after(12, TimeUnit.MICROSECONDS, ticker); |
| final Deadline d3 = Deadline.after(12, TimeUnit.MICROSECONDS, new FakeTicker()); |
| final Deadline d4 = Deadline.after(10, TimeUnit.MICROSECONDS, ticker); |
| |
| new EqualsTester() |
| .addEqualityGroup(d1, d2) |
| .addEqualityGroup(d3) |
| .addEqualityGroup(d4) |
| .testEquals(); |
| } |
| |
| private static class FakeTicker extends Deadline.Ticker { |
| private long time; |
| |
| @Override |
| public long nanoTime() { |
| 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); |
| } |
| |
| @Override |
| public String toString() { |
| return "FAKE_TICKER"; |
| } |
| } |
| } |