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)
diff --git a/src/main/java/com/google/devtools/build/skyframe/ErrorInfoManager.java b/src/main/java/com/google/devtools/build/skyframe/ErrorInfoManager.java
index 01ca34b..65193ef 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ErrorInfoManager.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ErrorInfoManager.java
@@ -14,7 +14,7 @@
 package com.google.devtools.build.skyframe;
 
 import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
-import java.util.Collection;
+import java.util.Set;
 import javax.annotation.Nullable;
 
 /** Used by {@link ParallelEvaluator} to produce and consume {@link ErrorInfo} instances. */
@@ -25,14 +25,11 @@
       boolean isTransitivelyTransient);
 
   /**
-   * Returns the {@link ErrorInfo} to use when there isn't currently one because
-   * {@link SkyFunction#compute} didn't throw a {@link SkyFunctionException} and there was no cycle.
+   * Returns the {@link ErrorInfo} to use when there isn't currently one because {@link
+   * SkyFunction#compute} didn't throw a {@link SkyFunctionException}.
    */
   @Nullable
-  ErrorInfo getErrorInfoToUse(
-      SkyKey skyKey,
-      boolean hasValue,
-      Collection<ErrorInfo> childErrorInfos);
+  ErrorInfo getErrorInfoToUse(SkyKey skyKey, boolean hasValue, Set<ErrorInfo> childErrorInfos);
 
   /**
    * Trivial {@link ErrorInfoManager} implementation whose {@link #fromException} simply uses
@@ -56,9 +53,7 @@
     @Override
     @Nullable
     public ErrorInfo getErrorInfoToUse(
-        SkyKey skyKey,
-        boolean hasValue,
-        Collection<ErrorInfo> childErrorInfos) {
+        SkyKey skyKey, boolean hasValue, Set<ErrorInfo> childErrorInfos) {
       return !childErrorInfos.isEmpty() ? ErrorInfo.fromChildErrors(skyKey, childErrorInfos) : null;
     }
   }
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
index eb25426..91767cc 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
@@ -85,7 +85,7 @@
   private final GroupedListHelper<SkyKey> newlyRequestedDeps = new GroupedListHelper<>();
 
   /** The set of errors encountered while fetching children. */
-  private final Collection<ErrorInfo> childErrorInfos = new LinkedHashSet<>();
+  private final Set<ErrorInfo> childErrorInfos = new LinkedHashSet<>();
 
   private final StoredEventHandler eventHandler =
       new StoredEventHandler() {