context: Implement equals and hashCode for Deadline (grpc#6075)
diff --git a/context/build.gradle b/context/build.gradle
index c38132a..1513cbc 100644
--- a/context/build.gradle
+++ b/context/build.gradle
@@ -1,7 +1,7 @@
 description = 'gRPC: Context'
 
 dependencies {
-    testCompile libraries.jsr305
+    testCompile libraries.jsr305, libraries.guava_testlib
     signature "org.codehaus.mojo.signature:java17:1.0@signature"
     signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
 }
diff --git a/context/src/main/java/io/grpc/Deadline.java b/context/src/main/java/io/grpc/Deadline.java
index 778bdd4..c7b50e0 100644
--- a/context/src/main/java/io/grpc/Deadline.java
+++ b/context/src/main/java/io/grpc/Deadline.java
@@ -16,6 +16,7 @@
 
 package io.grpc;
 
+import java.util.Arrays;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -231,6 +232,30 @@
     return 0;
   }
 
+  @Override
+  public int hashCode() {
+    return Arrays.asList(this.ticker, this.deadlineNanos).hashCode();
+  }
+
+  @Override
+  public boolean equals(final Object o) {
+    if (o == this) {
+      return true;
+    }
+    if (!(o instanceof Deadline)) {
+      return false;
+    }
+
+    final Deadline other = (Deadline) o;
+    if (this.ticker == null ? other.ticker != null : this.ticker != other.ticker) {
+      return false;
+    }
+    if (this.deadlineNanos != other.deadlineNanos) {
+      return false;
+    }
+    return true;
+  }
+
   /**
    * Time source representing nanoseconds since fixed but arbitrary point in time.
    *
diff --git a/context/src/test/java/io/grpc/DeadlineTest.java b/context/src/test/java/io/grpc/DeadlineTest.java
index 5f35918..69f849b 100644
--- a/context/src/test/java/io/grpc/DeadlineTest.java
+++ b/context/src/test/java/io/grpc/DeadlineTest.java
@@ -27,6 +27,7 @@
 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;
@@ -310,6 +311,20 @@
     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;
 
diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
index af212c0..b3e431b 100644
--- a/core/src/main/java/io/grpc/internal/ClientCallImpl.java
+++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
@@ -296,7 +296,7 @@
     context.addListener(cancellationListener, directExecutor());
     if (effectiveDeadline != null
         // If the context has the effective deadline, we don't need to schedule an extra task.
-        && context.getDeadline() != effectiveDeadline
+        && !effectiveDeadline.equals(context.getDeadline())
         // If the channel has been terminated, we don't need to schedule an extra task.
         && deadlineCancellationExecutor != null) {
       deadlineCancellationFuture = startDeadlineTimer(effectiveDeadline);
@@ -314,7 +314,7 @@
       Deadline effectiveDeadline, @Nullable Deadline outerCallDeadline,
       @Nullable Deadline callDeadline) {
     if (!log.isLoggable(Level.FINE) || effectiveDeadline == null
-        || outerCallDeadline != effectiveDeadline) {
+        || !effectiveDeadline.equals(outerCallDeadline)) {
       return;
     }