Add basic equality checking for ErrorInfo and tighten the interface for ErrorInfoManager.

PiperOrigin-RevId: 186524034
diff --git a/src/main/java/com/google/devtools/build/skyframe/ErrorInfo.java b/src/main/java/com/google/devtools/build/skyframe/ErrorInfo.java
index 47d6956..82d58ea 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ErrorInfo.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ErrorInfo.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
 import java.util.Collection;
+import java.util.Objects;
 import javax.annotation.Nullable;
 
 /**
@@ -126,6 +127,66 @@
   }
 
   @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof ErrorInfo)) {
+      return false;
+    }
+
+    ErrorInfo other = (ErrorInfo) obj;
+    if (rootCauses != other.rootCauses) {
+      if (rootCauses == null || other.rootCauses == null) {
+        return false;
+      }
+      if (!rootCauses.shallowEquals(other.rootCauses)) {
+        return false;
+      }
+    }
+
+    if (!Objects.equals(cycles, other.cycles)) {
+      return false;
+    }
+
+    // Don't check the specific exception as most exceptions don't implement equality but at least
+    // check their types and messages are the same.
+    if (exception != other.exception) {
+      if (exception == null || other.exception == null) {
+        return false;
+      }
+      // Class objects are singletons with a single class loader.
+      if (exception.getClass() != other.exception.getClass()) {
+        return false;
+      }
+      if (!Objects.equals(exception.getMessage(), other.exception.getMessage())) {
+        return false;
+      }
+    }
+
+    if (!Objects.equals(rootCauseOfException, other.rootCauseOfException)) {
+      return false;
+    }
+
+    return isDirectlyTransient == other.isDirectlyTransient
+        && isTransitivelyTransient == other.isTransitivelyTransient
+        && isCatastrophic == other.isCatastrophic;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        exception == null ? null : exception.getClass(),
+        exception == null ? "" : exception.getMessage(),
+        rootCauseOfException,
+        cycles,
+        isDirectlyTransient,
+        isTransitivelyTransient,
+        isCatastrophic,
+        rootCauses == null ? 0 : rootCauses.shallowHashCode());
+  }
+
+  @Override
   public String toString() {
     return MoreObjects.toStringHelper(this)
         .add("exception", exception)