Refactor QueryableGraph and ThinNodeQueryableGraph to be independent interfaces, in preparation for further changes.

--
MOS_MIGRATED_REVID=126924789
diff --git a/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
index 8e9ddb6..d714924 100644
--- a/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
@@ -157,22 +157,22 @@
 
   @Override
   public Iterable<SkyKey> getDirectDeps() {
-    return getThinDelegate().getDirectDeps();
+    return getDelegate().getDirectDeps();
   }
 
   @Override
   public void removeReverseDep(SkyKey reverseDep) {
-    getThinDelegate().removeReverseDep(reverseDep);
+    getDelegate().removeReverseDep(reverseDep);
   }
 
   @Override
   public void removeInProgressReverseDep(SkyKey reverseDep) {
-    getThinDelegate().removeInProgressReverseDep(reverseDep);
+    getDelegate().removeInProgressReverseDep(reverseDep);
   }
 
   @Override
   public Iterable<SkyKey> getReverseDeps() {
-    return getThinDelegate().getReverseDeps();
+    return getDelegate().getReverseDeps();
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/skyframe/DirtiableGraph.java b/src/main/java/com/google/devtools/build/skyframe/DeletableGraph.java
similarity index 82%
rename from src/main/java/com/google/devtools/build/skyframe/DirtiableGraph.java
rename to src/main/java/com/google/devtools/build/skyframe/DeletableGraph.java
index fe7e88b..d091f08 100644
--- a/src/main/java/com/google/devtools/build/skyframe/DirtiableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/DeletableGraph.java
@@ -19,13 +19,11 @@
  * Interface for classes that need to remove values from graph. Currently just used by {@link
  * EagerInvalidator}.
  *
- * <p>This class is not intended for direct use, and is only exposed as public for use in
- * evaluation implementations outside of this package.
+ * <p>This class is not intended for direct use, and is only exposed as public for use in evaluation
+ * implementations outside of this package.
  */
 @ThreadSafe
-public interface DirtiableGraph extends QueryableGraph {
-  /**
-   * Remove the value with given name from the graph.
-   */
+public interface DeletableGraph {
+  /** Remove the value with given name from the graph. */
   void remove(SkyKey key);
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/EagerInvalidator.java b/src/main/java/com/google/devtools/build/skyframe/EagerInvalidator.java
index bb6d2b2..b7c1ee9 100644
--- a/src/main/java/com/google/devtools/build/skyframe/EagerInvalidator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/EagerInvalidator.java
@@ -43,9 +43,14 @@
    * long as the full upward transitive closure of the nodes is specified for deletion, the graph
    * remains consistent.
    */
-  public static void delete(DirtiableGraph graph, Iterable<SkyKey> diff,
-      EvaluationProgressReceiver invalidationReceiver, InvalidationState state,
-      boolean traverseGraph, DirtyKeyTracker dirtyKeyTracker) throws InterruptedException {
+  public static void delete(
+      InMemoryGraph graph,
+      Iterable<SkyKey> diff,
+      EvaluationProgressReceiver invalidationReceiver,
+      InvalidationState state,
+      boolean traverseGraph,
+      DirtyKeyTracker dirtyKeyTracker)
+      throws InterruptedException {
     DeletingNodeVisitor visitor =
         createDeletingVisitorIfNeeded(
             graph, diff, invalidationReceiver, state, traverseGraph, dirtyKeyTracker);
@@ -56,7 +61,7 @@
 
   @Nullable
   static DeletingNodeVisitor createDeletingVisitorIfNeeded(
-      DirtiableGraph graph,
+      InMemoryGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
@@ -70,7 +75,7 @@
 
   @Nullable
   static DirtyingNodeVisitor createInvalidatingVisitorIfNeeded(
-      ThinNodeQueryableGraph graph,
+      InvalidatableGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
@@ -84,7 +89,7 @@
 
   @Nullable
   private static DirtyingNodeVisitor createInvalidatingVisitorIfNeeded(
-      ThinNodeQueryableGraph graph,
+      InvalidatableGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
@@ -110,7 +115,7 @@
    * an executor constructed with the provided factory.
    */
   public static void invalidate(
-      ThinNodeQueryableGraph graph,
+      InvalidatableGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
@@ -130,7 +135,7 @@
    * the provided {@link ForkJoinPool}.
    */
   public static void invalidate(
-      ThinNodeQueryableGraph graph,
+      InvalidatableGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
@@ -154,11 +159,9 @@
     }
   }
 
-  /**
-   * Invalidates given values and their upward transitive closure in the graph.
-   */
+  /** Invalidates given values and their upward transitive closure in the graph. */
   public static void invalidate(
-      DirtiableGraph graph,
+      InvalidatableGraph graph,
       Iterable<SkyKey> diff,
       EvaluationProgressReceiver invalidationReceiver,
       InvalidationState state,
diff --git a/src/main/java/com/google/devtools/build/skyframe/EvaluableGraph.java b/src/main/java/com/google/devtools/build/skyframe/EvaluableGraph.java
index 7527146..b3d8bd0 100644
--- a/src/main/java/com/google/devtools/build/skyframe/EvaluableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/EvaluableGraph.java
@@ -22,7 +22,7 @@
  * single version of the graph.
  */
 @ThreadSafe
-interface EvaluableGraph extends QueryableGraph {
+interface EvaluableGraph extends QueryableGraph, DeletableGraph {
   /**
    * Like {@link QueryableGraph#getBatch}, except it creates a new node for each key not already
    * present in the graph. Thus, the returned map will have an entry for each key in {@code keys}.
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryGraph.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryGraph.java
index 02735c0..5d508a9 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryGraph.java
@@ -16,7 +16,7 @@
 import java.util.Map;
 
 /** {@link ProcessableGraph} that exposes the contents of the entire graph. */
-interface InMemoryGraph extends ProcessableGraph {
+interface InMemoryGraph extends ProcessableGraph, InvalidatableGraph {
   /**
    * Returns a read-only live view of the nodes in the graph. All node are included. Dirty values
    * include their Node value. Values in error have a null value.
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
index a5e22221..5d853b7 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
@@ -284,9 +284,8 @@
   }
 
   @Override
-  public void injectGraphTransformerForTesting(
-      Function<ThinNodeQueryableGraph, ProcessableGraph> transformer) {
-    this.graph = (InMemoryGraph) transformer.apply(this.graph);
+  public void injectGraphTransformerForTesting(GraphTransformerForTesting transformer) {
+    this.graph = transformer.transform(this.graph);
   }
 
   public ProcessableGraph getGraphForTesting() {
diff --git a/src/main/java/com/google/devtools/build/skyframe/ThinNodeQueryableGraph.java b/src/main/java/com/google/devtools/build/skyframe/InvalidatableGraph.java
similarity index 78%
rename from src/main/java/com/google/devtools/build/skyframe/ThinNodeQueryableGraph.java
rename to src/main/java/com/google/devtools/build/skyframe/InvalidatableGraph.java
index eeb3ef5..2437407 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ThinNodeQueryableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InvalidatableGraph.java
@@ -17,20 +17,14 @@
 
 import java.util.Map;
 
-import javax.annotation.Nullable;
-
 /**
- * A graph that exposes thin representations of its entries and structure, for use by classes that
- * must traverse it, but not read its entries' values.
+ * A graph that exposes thin representations of its entries and structure, for use during
+ * invalidation.
+ *
+ * <p>Public only for use in alternative graph implementations.
  */
 @ThreadSafe
-public interface ThinNodeQueryableGraph {
-  /**
-   * Returns the thin node with the given name, or {@code null} if the node does not exist.
-   */
-  @Nullable
-  ThinNodeEntry get(SkyKey key);
-
+public interface InvalidatableGraph {
   /**
    * Fetches all the given thin nodes. Returns a map {@code m} such that, for all {@code k} in
    * {@code keys}, {@code m.get(k).equals(e)} iff {@code get(k) == e} and {@code e != null}, and
diff --git a/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java b/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
index 6eec236..1fe90d1 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
@@ -62,7 +62,7 @@
  *
  * <p>This is intended only for use in alternative {@code MemoizingEvaluator} implementations.
  */
-public abstract class InvalidatingNodeVisitor<TGraph extends ThinNodeQueryableGraph> {
+public abstract class InvalidatingNodeVisitor<TGraph extends InvalidatableGraph> {
 
   // Default thread count is equal to the number of cores to exploit
   // that level of hardware parallelism, since invalidation should be CPU-bound.
@@ -236,13 +236,13 @@
   }
 
   /** A node-deleting implementation. */
-  static class DeletingNodeVisitor extends InvalidatingNodeVisitor<DirtiableGraph> {
+  static class DeletingNodeVisitor extends InvalidatingNodeVisitor<InMemoryGraph> {
 
     private final Set<SkyKey> visited = Sets.newConcurrentHashSet();
     private final boolean traverseGraph;
 
     DeletingNodeVisitor(
-        DirtiableGraph graph,
+        InMemoryGraph graph,
         EvaluationProgressReceiver invalidationReceiver,
         InvalidationState state,
         boolean traverseGraph,
@@ -338,7 +338,7 @@
   }
 
   /** A node-dirtying implementation. */
-  static class DirtyingNodeVisitor extends InvalidatingNodeVisitor<ThinNodeQueryableGraph> {
+  static class DirtyingNodeVisitor extends InvalidatingNodeVisitor<InvalidatableGraph> {
 
     private final Set<SkyKey> changed =
         Collections.newSetFromMap(
@@ -351,7 +351,7 @@
     private final boolean supportInterruptions;
 
     protected DirtyingNodeVisitor(
-        ThinNodeQueryableGraph graph,
+        InvalidatableGraph graph,
         EvaluationProgressReceiver invalidationReceiver,
         InvalidationState state,
         DirtyKeyTracker dirtyKeyTracker,
@@ -365,7 +365,7 @@
      * passing {@code false} for {@param supportInterruptions}.
      */
     protected DirtyingNodeVisitor(
-        ThinNodeQueryableGraph graph,
+        InvalidatableGraph graph,
         EvaluationProgressReceiver invalidationReceiver,
         InvalidationState state,
         DirtyKeyTracker dirtyKeyTracker,
diff --git a/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
index 269ea09..4d5d21c 100644
--- a/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
@@ -14,7 +14,6 @@
 package com.google.devtools.build.skyframe;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetVisitor;
@@ -126,19 +125,18 @@
 
   /**
    * Tests that want finer control over the graph being used may provide a {@code transformer} here.
-   * This {@code transformer} will be applied to the graph for each invalidation/evaluation. While
-   * the graph returned by {@code transformer#apply} must technically be a {@link ProcessableGraph},
-   * if a {@link ThinNodeQueryableGraph} was given as the argument to {@code transformer#apply},
-   * then only the methods in {@link ThinNodeQueryableGraph} will be called on the returned graph,
-   * in other words it will be treated as a {@link ThinNodeQueryableGraph}. Thus, the returned graph
-   * is free not to actually implement the remaining methods in {@link ProcessableGraph} in that
-   * case.
-   *
-   * <p>Similarly, if the argument to {@code transformer#apply} is an {@link InMemoryGraph}, then
-   * the resulting graph must be an {@link InMemoryGraph}.
-   * */
-  void injectGraphTransformerForTesting(
-      Function<ThinNodeQueryableGraph, ProcessableGraph> transformer);
+   * This {@code transformer} will be applied to the graph for each invalidation/evaluation.
+   */
+  void injectGraphTransformerForTesting(GraphTransformerForTesting transformer);
+
+  /** Transforms a graph, possibly injecting other functionality. */
+  interface GraphTransformerForTesting {
+    InMemoryGraph transform(InMemoryGraph graph);
+
+    InvalidatableGraph transform(InvalidatableGraph graph);
+
+    ProcessableGraph transform(ProcessableGraph graph);
+  }
 
   /**
    * Write the graph to the output stream. Not necessarily thread-safe. Use only for debugging
diff --git a/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
index 7b38324..8b7b091 100644
--- a/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
@@ -83,6 +83,37 @@
   @ThreadSafe
   SkyValue getValue();
 
+  /**
+   * Returns an immutable iterable of the direct deps of this node. This method may only be called
+   * after the evaluation of this node is complete.
+   *
+   * <p>This method is not very efficient, but is only be called in limited circumstances -- when
+   * the node is about to be deleted, or when the node is expected to have no direct deps (in which
+   * case the overhead is not so bad). It should not be called repeatedly for the same node, since
+   * each call takes time proportional to the number of direct deps of the node.
+   */
+  @ThreadSafe
+  Iterable<SkyKey> getDirectDeps();
+
+  /** Removes a reverse dependency. */
+  @ThreadSafe
+  void removeReverseDep(SkyKey reverseDep);
+
+  /**
+   * Removes a reverse dependency.
+   *
+   * <p>May only be called if this entry is not done (i.e. {@link #isDone} is false) and {@param
+   * reverseDep} is present in {@link #getReverseDeps}
+   */
+  @ThreadSafe
+  void removeInProgressReverseDep(SkyKey reverseDep);
+
+  /**
+   * Returns a copy of the set of reverse dependencies. Note that this introduces a potential
+   * check-then-act race; {@link #removeReverseDep} may fail for a key that is returned here.
+   */
+  @ThreadSafe
+  Iterable<SkyKey> getReverseDeps();
 
   /**
    * Returns raw {@link SkyValue} stored in this entry, which may include metadata associated with
@@ -236,22 +267,22 @@
 
   /**
    * Should only be called if the entry is dirty. During the examination to see if the entry must be
-   * re-evaluated, this method returns the next group of children to be checked. Callers should
-   * have already called {@link #getDirtyState} and received a return value of
-   * {@link DirtyState#CHECK_DEPENDENCIES} before calling this method -- any other
-   * return value from {@link #getDirtyState} means that this method must not be called, since
-   * whether or not the node needs to be rebuilt is already known.
+   * re-evaluated, this method returns the next group of children to be checked. Callers should have
+   * already called {@link #getDirtyState} and received a return value of {@link
+   * DirtyState#CHECK_DEPENDENCIES} before calling this method -- any other return value from {@link
+   * #getDirtyState} means that this method must not be called, since whether or not the node needs
+   * to be rebuilt is already known.
    *
-   * <p>Deps are returned in groups. The deps in each group were requested in parallel by the
-   * {@code SkyFunction} last build, meaning independently of the values of any other deps in this
-   * group (although possibly depending on deps in earlier groups). Thus the caller may check all
-   * the deps in this group in parallel, since the deps in all previous groups are verified
-   * unchanged. See {@link SkyFunction.Environment#getValues} for more on dependency groups.
+   * <p>Deps are returned in groups. The deps in each group were requested in parallel by the {@code
+   * SkyFunction} last build, meaning independently of the values of any other deps in this group
+   * (although possibly depending on deps in earlier groups). Thus the caller may check all the deps
+   * in this group in parallel, since the deps in all previous groups are verified unchanged. See
+   * {@link SkyFunction.Environment#getValues} for more on dependency groups.
    *
    * <p>The caller should register these as deps of this entry using {@link #addTemporaryDirectDeps}
    * before checking them.
    *
-   * @see BuildingState#getNextDirtyDirectDeps()
+   * @see DirtyBuildingState#getNextDirtyDirectDeps()
    */
   @ThreadSafe
   Collection<SkyKey> getNextDirtyDirectDeps();
diff --git a/src/main/java/com/google/devtools/build/skyframe/ProcessableGraph.java b/src/main/java/com/google/devtools/build/skyframe/ProcessableGraph.java
index bc391a8..532a151 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ProcessableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ProcessableGraph.java
@@ -19,9 +19,8 @@
  * A graph that is both Dirtiable (values can be deleted) and Evaluable (values can be added). All
  * methods in this interface (as inherited from super-interfaces) should be thread-safe.
  *
- * <p>This class is not intended for direct use, and is only exposed as public for use in
- * evaluation implementations outside of this package.
+ * <p>This class is not intended for direct use, and is only exposed as public for use in evaluation
+ * implementations outside of this package.
  */
 @ThreadSafe
-public interface ProcessableGraph extends DirtiableGraph, EvaluableGraph {
-}
+public interface ProcessableGraph extends DeletableGraph, EvaluableGraph {}
diff --git a/src/main/java/com/google/devtools/build/skyframe/QueryableGraph.java b/src/main/java/com/google/devtools/build/skyframe/QueryableGraph.java
index ee09699..0419828 100644
--- a/src/main/java/com/google/devtools/build/skyframe/QueryableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/QueryableGraph.java
@@ -19,16 +19,11 @@
 
 import javax.annotation.Nullable;
 
-/**
- * A graph that exposes its entries and structure, for use by classes that must traverse it.
- */
+/** A graph that exposes its entries and structure, for use by classes that must traverse it. */
 @ThreadSafe
-public interface QueryableGraph extends ThinNodeQueryableGraph {
-  /**
-   * Returns the node with the given name, or {@code null} if the node does not exist.
-   */
+public interface QueryableGraph {
+  /** Returns the node with the given name, or {@code null} if the node does not exist. */
   @Nullable
-  @Override
   NodeEntry get(SkyKey key);
 
   /**
@@ -36,6 +31,5 @@
    * {@code keys}, {@code m.get(k).equals(e)} iff {@code get(k) == e} and {@code e != null}, and
    * {@code !m.containsKey(k)} iff {@code get(k) == null}.
    */
-  @Override
   Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys);
 }
diff --git a/src/main/java/com/google/devtools/build/skyframe/ThinNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/ThinNodeEntry.java
index 1a5c075..f81355c 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ThinNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ThinNodeEntry.java
@@ -32,40 +32,6 @@
   boolean isDone();
 
   /**
-   * Returns an immutable iterable of the direct deps of this node. This method may only be called
-   * after the evaluation of this node is complete.
-   *
-   * <p>This method is not very efficient, but is only be called in limited circumstances --
-   * when the node is about to be deleted, or when the node is expected to have no direct deps (in
-   * which case the overhead is not so bad). It should not be called repeatedly for the same node,
-   * since each call takes time proportional to the number of direct deps of the node.
-   */
-  @ThreadSafe
-  Iterable<SkyKey> getDirectDeps();
-
-  /**
-   * Removes a reverse dependency.
-   */
-  @ThreadSafe
-  void removeReverseDep(SkyKey reverseDep);
-
-  /**
-   * Removes a reverse dependency.
-   *
-   * <p>May only be called if this entry is not done (i.e. {@link #isDone} is false) and
-   * {@param reverseDep} is present in {@link #getReverseDeps}
-   */
-  @ThreadSafe
-  void removeInProgressReverseDep(SkyKey reverseDep);
-
-  /**
-   * Returns a copy of the set of reverse dependencies. Note that this introduces a potential
-   * check-then-act race; {@link #removeReverseDep} may fail for a key that is returned here.
-   */
-  @ThreadSafe
-  Iterable<SkyKey> getReverseDeps();
-
-  /**
    * Returns true if the entry is marked dirty, meaning that at least one of its transitive
    * dependencies is marked changed.
    */
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
index 75a5f67..f48c02c 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java
@@ -53,9 +53,9 @@
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.build.skyframe.NotifyingGraph.EventType;
-import com.google.devtools.build.skyframe.NotifyingGraph.Listener;
-import com.google.devtools.build.skyframe.NotifyingGraph.Order;
+import com.google.devtools.build.skyframe.NotifyingHelper.EventType;
+import com.google.devtools.build.skyframe.NotifyingHelper.Listener;
+import com.google.devtools.build.skyframe.NotifyingHelper.Order;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.TrackingAwaiter;
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java
index be2f423..d8acbcf 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestBase.java
@@ -38,9 +38,9 @@
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.build.skyframe.DeterministicGraph;
+import com.google.devtools.build.skyframe.DeterministicHelper;
 import com.google.devtools.build.skyframe.InMemoryMemoizingEvaluator;
-import com.google.devtools.build.skyframe.NotifyingGraph.Listener;
+import com.google.devtools.build.skyframe.NotifyingHelper.Listener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -131,7 +131,7 @@
     InMemoryMemoizingEvaluator memoizingEvaluator =
         (InMemoryMemoizingEvaluator) skyframeExecutor.getEvaluatorForTesting();
     memoizingEvaluator.injectGraphTransformerForTesting(
-        DeterministicGraph.makeTransformer(listener, deterministic));
+        DeterministicHelper.makeTransformer(listener, deterministic));
   }
 
   protected void runTestForMultiCpuAnalysisFailure(String badCpu, String goodCpu) throws Exception {
diff --git a/src/test/java/com/google/devtools/build/skyframe/BUILD b/src/test/java/com/google/devtools/build/skyframe/BUILD
index 21b6342..4286728 100644
--- a/src/test/java/com/google/devtools/build/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/skyframe/BUILD
@@ -12,7 +12,7 @@
     "TrackingInvalidationReceiver.java",
     "WalkableGraphUtils.java",
     # Truth Subject, SubjectFactory, and Graph files.
-] + glob(["*Subject.java"]) + glob(["*SubjectFactory.java"]) + glob(["*Graph.java"])
+] + glob(["*Subject.java"]) + glob(["*SubjectFactory.java"]) + glob(["*Graph.java"]) + glob(["*Helper.java"])
 
 java_library(
     name = "testutil",
diff --git a/src/test/java/com/google/devtools/build/skyframe/DeterministicGraph.java b/src/test/java/com/google/devtools/build/skyframe/DeterministicGraph.java
deleted file mode 100644
index 85df5c5..0000000
--- a/src/test/java/com/google/devtools/build/skyframe/DeterministicGraph.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// 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 com.google.devtools.build.skyframe;
-
-import com.google.common.base.Function;
-import com.google.common.collect.Iterables;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.annotation.Nullable;
-
-/**
- * {@link NotifyingGraph} that returns reverse deps, temporary direct deps, and the results of
- * batch requests ordered alphabetically by sky key string representation.
- */
-public class DeterministicGraph<TGraph extends ThinNodeQueryableGraph>
-    extends NotifyingGraph<TGraph> {
-  public static final Function<ThinNodeQueryableGraph, ProcessableGraph> MAKE_DETERMINISTIC =
-      new Function<ThinNodeQueryableGraph, ProcessableGraph>() {
-        @Override
-        public ProcessableGraph apply(ThinNodeQueryableGraph queryableGraph) {
-          if (queryableGraph instanceof InMemoryGraph) {
-            return new DeterministicInMemoryGraph((InMemoryGraph) queryableGraph);
-          } else {
-            return new DeterministicGraph<>(queryableGraph);
-          }
-        }
-      };
-
-  public static Function<ThinNodeQueryableGraph, ProcessableGraph> makeTransformer(
-      final Listener listener, boolean deterministic) {
-    if (deterministic) {
-      return new Function<ThinNodeQueryableGraph, ProcessableGraph>() {
-        @Override
-        public ProcessableGraph apply(ThinNodeQueryableGraph queryableGraph) {
-          if (queryableGraph instanceof InMemoryGraph) {
-            return new DeterministicInMemoryGraph((InMemoryGraph) queryableGraph, listener);
-          } else {
-            return new DeterministicGraph<>(queryableGraph, listener);
-          }
-        }
-      };
-    } else {
-      return NotifyingGraph.makeNotifyingTransformer(listener);
-    }
-  }
-
-  private static final Comparator<SkyKey> ALPHABETICAL_SKYKEY_COMPARATOR =
-      new Comparator<SkyKey>() {
-        @Override
-        public int compare(SkyKey o1, SkyKey o2) {
-          return o1.toString().compareTo(o2.toString());
-        }
-      };
-
-  DeterministicGraph(TGraph delegate, Listener listener) {
-    super(delegate, listener);
-  }
-
-  DeterministicGraph(TGraph delegate) {
-    super(delegate, NotifyingGraph.Listener.NULL_LISTENER);
-  }
-
-  @Nullable
-  @Override
-  protected DeterministicValueEntry wrapEntry(SkyKey key, @Nullable ThinNodeEntry entry) {
-    return entry == null ? null : new DeterministicValueEntry(key, entry);
-  }
-
-  private static Map<SkyKey, NodeEntry> makeDeterministic(Map<SkyKey, NodeEntry> map) {
-    Map<SkyKey, NodeEntry> result = new TreeMap<>(ALPHABETICAL_SKYKEY_COMPARATOR);
-    result.putAll(map);
-    return result;
-  }
-
-  @Override
-  public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
-    return makeDeterministic(super.getBatch(keys));
-  }
-
-  @Override
-  public Map<SkyKey, NodeEntry> createIfAbsentBatch(Iterable<SkyKey> keys) {
-    return makeDeterministic(super.createIfAbsentBatch(keys));
-  }
-
-  /**
-   * This class uses TreeSet to store reverse dependencies of NodeEntry. As a result all values are
-   * lexicographically sorted.
-   */
-  private class DeterministicValueEntry extends NotifyingNodeEntry {
-    private DeterministicValueEntry(SkyKey myKey, ThinNodeEntry delegate) {
-      super(myKey, delegate);
-    }
-
-    @Override
-    public synchronized Collection<SkyKey> getReverseDeps() {
-      TreeSet<SkyKey> result = new TreeSet<>(ALPHABETICAL_SKYKEY_COMPARATOR);
-      Iterables.addAll(result, super.getReverseDeps());
-      return result;
-    }
-
-    @Override
-    public synchronized Set<SkyKey> getInProgressReverseDeps() {
-      TreeSet<SkyKey> result = new TreeSet<>(ALPHABETICAL_SKYKEY_COMPARATOR);
-      result.addAll(super.getInProgressReverseDeps());
-      return result;
-    }
-  }
-}
diff --git a/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java b/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java
new file mode 100644
index 0000000..655333a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java
@@ -0,0 +1,146 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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 com.google.devtools.build.skyframe;
+
+import com.google.common.collect.Iterables;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link NotifyingHelper} that returns reverse deps, temporary direct deps, and the results of
+ * batch requests ordered alphabetically by sky key string representation.
+ */
+public class DeterministicHelper extends NotifyingHelper {
+  static final MemoizingEvaluator.GraphTransformerForTesting MAKE_DETERMINISTIC =
+      makeTransformer(Listener.NULL_LISTENER, /*deterministic=*/ true);
+
+  public static MemoizingEvaluator.GraphTransformerForTesting makeTransformer(
+      final Listener listener, boolean deterministic) {
+    if (deterministic) {
+      return new MemoizingEvaluator.GraphTransformerForTesting() {
+        @Override
+        public InMemoryGraph transform(InMemoryGraph graph) {
+          return new DeterministicInMemoryGraph(graph, listener);
+        }
+
+        @Override
+        public InvalidatableGraph transform(InvalidatableGraph graph) {
+          return new DeterministicInvalidatableGraph(graph, listener);
+        }
+
+        @Override
+        public ProcessableGraph transform(ProcessableGraph graph) {
+          return new DeterministicProcessableGraph(graph, listener);
+        }
+      };
+    } else {
+      return NotifyingHelper.makeNotifyingTransformer(listener);
+    }
+  }
+
+  private static final Comparator<SkyKey> ALPHABETICAL_SKYKEY_COMPARATOR =
+      new Comparator<SkyKey>() {
+        @Override
+        public int compare(SkyKey o1, SkyKey o2) {
+          return o1.toString().compareTo(o2.toString());
+        }
+      };
+
+  DeterministicHelper(Listener listener) {
+    super(listener);
+  }
+
+  DeterministicHelper() {
+    super(NotifyingHelper.Listener.NULL_LISTENER);
+  }
+
+  @Nullable
+  @Override
+  protected DeterministicValueEntry wrapEntry(SkyKey key, @Nullable ThinNodeEntry entry) {
+    return entry == null ? null : new DeterministicValueEntry(key, entry);
+  }
+
+  private static Map<SkyKey, NodeEntry> makeDeterministic(Map<SkyKey, NodeEntry> map) {
+    Map<SkyKey, NodeEntry> result = new TreeMap<>(ALPHABETICAL_SKYKEY_COMPARATOR);
+    result.putAll(map);
+    return result;
+  }
+
+  private static class DeterministicInvalidatableGraph extends NotifyingInvalidatableGraph {
+    DeterministicInvalidatableGraph(InvalidatableGraph delegate, Listener graphListener) {
+      super(delegate, new DeterministicHelper(graphListener));
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
+      return makeDeterministic(super.getBatch(keys));
+    }
+  }
+
+  static class DeterministicProcessableGraph extends NotifyingProcessableGraph {
+    DeterministicProcessableGraph(ProcessableGraph delegate, Listener graphListener) {
+      super(delegate, new DeterministicHelper(graphListener));
+    }
+
+    DeterministicProcessableGraph(ProcessableGraph delegate) {
+      this(delegate, Listener.NULL_LISTENER);
+    }
+
+    @Override
+    public void remove(SkyKey key) {
+      delegate.remove(key);
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> createIfAbsentBatch(Iterable<SkyKey> keys) {
+      return makeDeterministic(super.createIfAbsentBatch(keys));
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
+      return makeDeterministic(super.getBatch(keys));
+    }
+  }
+
+  /**
+   * This class uses TreeSet to store reverse dependencies of NodeEntry. As a result all values are
+   * lexicographically sorted.
+   */
+  private class DeterministicValueEntry extends NotifyingNodeEntry {
+    private DeterministicValueEntry(SkyKey myKey, ThinNodeEntry delegate) {
+      super(myKey, delegate);
+    }
+
+    @Override
+    public synchronized Collection<SkyKey> getReverseDeps() {
+      TreeSet<SkyKey> result = new TreeSet<>(ALPHABETICAL_SKYKEY_COMPARATOR);
+      Iterables.addAll(result, super.getReverseDeps());
+      return result;
+    }
+
+    @Override
+    public synchronized Set<SkyKey> getInProgressReverseDeps() {
+      TreeSet<SkyKey> result = new TreeSet<>(ALPHABETICAL_SKYKEY_COMPARATOR);
+      result.addAll(super.getInProgressReverseDeps());
+      return result;
+    }
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/skyframe/DeterministicInMemoryGraph.java b/src/test/java/com/google/devtools/build/skyframe/DeterministicInMemoryGraph.java
index 9e16284..a023383 100644
--- a/src/test/java/com/google/devtools/build/skyframe/DeterministicInMemoryGraph.java
+++ b/src/test/java/com/google/devtools/build/skyframe/DeterministicInMemoryGraph.java
@@ -16,32 +16,28 @@
 import java.util.Map;
 
 /**
- * {@link DeterministicGraph} that implements the {@link InMemoryGraph} interface. Sadly, cannot be
- * a {@link NotifyingInMemoryGraph} due to Java's forbidding multiple inheritance.
+ * {@link DeterministicHelper.DeterministicProcessableGraph} that implements the {@link
+ * InMemoryGraph} interface. Sadly, cannot be a {@link NotifyingInMemoryGraph} due to Java's
+ * forbidding multiple inheritance.
  */
-class DeterministicInMemoryGraph extends DeterministicGraph<InMemoryGraph>
+class DeterministicInMemoryGraph extends DeterministicHelper.DeterministicProcessableGraph
     implements InMemoryGraph {
-
-  DeterministicInMemoryGraph(InMemoryGraph delegate, Listener graphListener) {
+  DeterministicInMemoryGraph(InMemoryGraph delegate, NotifyingHelper.Listener graphListener) {
     super(delegate, graphListener);
   }
 
-  DeterministicInMemoryGraph(InMemoryGraph delegate) {
-    super(delegate);
-  }
-
   @Override
   public Map<SkyKey, SkyValue> getValues() {
-    return delegate.getValues();
+    return ((InMemoryGraph) delegate).getValues();
   }
 
   @Override
   public Map<SkyKey, SkyValue> getDoneValues() {
-    return delegate.getDoneValues();
+    return ((InMemoryGraph) delegate).getDoneValues();
   }
 
   @Override
   public Map<SkyKey, NodeEntry> getAllValues() {
-    return delegate.getAllValues();
+    return ((InMemoryGraph) delegate).getAllValues();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java b/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
index 1976988..dbc418f 100644
--- a/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/EagerInvalidatorTest.java
@@ -84,8 +84,11 @@
   }
 
   @SuppressWarnings("unused") // Overridden by subclasses.
-  void invalidate(DirtiableGraph graph, EvaluationProgressReceiver invalidationReceiver,
-      SkyKey... keys) throws InterruptedException { throw new UnsupportedOperationException(); }
+  void invalidate(
+      InMemoryGraph graph, EvaluationProgressReceiver invalidationReceiver, SkyKey... keys)
+      throws InterruptedException {
+    throw new UnsupportedOperationException();
+  }
 
   boolean gcExpected() { throw new UnsupportedOperationException(); }
 
@@ -595,8 +598,9 @@
   @RunWith(JUnit4.class)
   public static class DeletingInvalidatorTest extends EagerInvalidatorTest {
     @Override
-    protected void invalidate(DirtiableGraph graph, EvaluationProgressReceiver invalidationReceiver,
-        SkyKey... keys) throws InterruptedException {
+    protected void invalidate(
+        InMemoryGraph graph, EvaluationProgressReceiver invalidationReceiver, SkyKey... keys)
+        throws InterruptedException {
       Iterable<SkyKey> diff = ImmutableList.copyOf(keys);
       DeletingNodeVisitor deletingNodeVisitor =
           EagerInvalidator.createDeletingVisitorIfNeeded(
@@ -665,8 +669,9 @@
   @RunWith(JUnit4.class)
   public static class DirtyingInvalidatorTest extends EagerInvalidatorTest {
     @Override
-    protected void invalidate(DirtiableGraph graph, EvaluationProgressReceiver invalidationReceiver,
-        SkyKey... keys) throws InterruptedException {
+    protected void invalidate(
+        InMemoryGraph graph, EvaluationProgressReceiver invalidationReceiver, SkyKey... keys)
+        throws InterruptedException {
       Iterable<SkyKey> diff = ImmutableList.copyOf(keys);
       DirtyingNodeVisitor dirtyingNodeVisitor =
           EagerInvalidator.createInvalidatingVisitorIfNeeded(
diff --git a/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java b/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
index 61c9821..ffdad2a 100644
--- a/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/MemoizingEvaluatorTest.java
@@ -51,9 +51,9 @@
 import com.google.devtools.build.skyframe.GraphTester.StringValue;
 import com.google.devtools.build.skyframe.GraphTester.TestFunction;
 import com.google.devtools.build.skyframe.GraphTester.ValueComputer;
-import com.google.devtools.build.skyframe.NotifyingGraph.EventType;
-import com.google.devtools.build.skyframe.NotifyingGraph.Listener;
-import com.google.devtools.build.skyframe.NotifyingGraph.Order;
+import com.google.devtools.build.skyframe.NotifyingHelper.EventType;
+import com.google.devtools.build.skyframe.NotifyingHelper.Listener;
+import com.google.devtools.build.skyframe.NotifyingHelper.Order;
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 
@@ -1248,7 +1248,7 @@
     // Mark dep1 changed, so otherTop will be dirty and request re-evaluation of dep1.
     tester.getOrCreate(dep1, /*markAsModified=*/true);
     SkyKey topKey = GraphTester.toSkyKey("top");
-    // Note that since DeterministicGraph alphabetizes reverse deps, it is important that
+    // Note that since DeterministicHelper alphabetizes reverse deps, it is important that
     // "cycle2" comes before "top".
     final SkyKey cycle1Key = GraphTester.toSkyKey("cycle1");
     final SkyKey cycle2Key = GraphTester.toSkyKey("cycle2");
@@ -4013,11 +4013,11 @@
 
   private void injectGraphListenerForTesting(Listener listener, boolean deterministic) {
     tester.evaluator.injectGraphTransformerForTesting(
-        DeterministicGraph.makeTransformer(listener, deterministic));
+        DeterministicHelper.makeTransformer(listener, deterministic));
   }
 
   private void makeGraphDeterministic() {
-    tester.evaluator.injectGraphTransformerForTesting(DeterministicGraph.MAKE_DETERMINISTIC);
+    tester.evaluator.injectGraphTransformerForTesting(DeterministicHelper.MAKE_DETERMINISTIC);
   }
 
   private static final class PassThroughSelected implements ValueComputer {
diff --git a/src/test/java/com/google/devtools/build/skyframe/NotifyingGraph.java b/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
similarity index 72%
rename from src/test/java/com/google/devtools/build/skyframe/NotifyingGraph.java
rename to src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
index 22dbc35..2f973b5 100644
--- a/src/test/java/com/google/devtools/build/skyframe/NotifyingGraph.java
+++ b/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.skyframe;
 
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Maps;
@@ -28,36 +27,34 @@
 
 /**
  * Class that allows clients to be notified on each access of the graph. Clients can simply track
- * accesses, or they can block to achieve desired synchronization. Clients should call
- * {@link TrackingAwaiter#INSTANCE#assertNoErrors} at the end of tests in case exceptions were
- * swallowed in async threads.
- *
- * <p>While this class nominally always implements a {@link ProcessableGraph}, it will throw if any
- * of the methods in {@link ProcessableGraph} that are not in {@link ThinNodeQueryableGraph} are
- * called on it and its {@link #delegate} is not a {@link ProcessableGraph}. This lack of type
- * safety is so that a {@code NotifyingGraph} can be returned by {@link #makeNotifyingTransformer}
- * and used in {@link MemoizingEvaluator#injectGraphTransformerForTesting}.
+ * accesses, or they can block to achieve desired synchronization. Clients should call {@link
+ * TrackingAwaiter#INSTANCE#assertNoErrors} at the end of tests in case exceptions were swallowed in
+ * async threads.
  */
-public class NotifyingGraph<TGraph extends ThinNodeQueryableGraph> implements ProcessableGraph {
-  public static Function<ThinNodeQueryableGraph, ProcessableGraph> makeNotifyingTransformer(
+public class NotifyingHelper {
+  public static MemoizingEvaluator.GraphTransformerForTesting makeNotifyingTransformer(
       final Listener listener) {
-    return new Function<ThinNodeQueryableGraph, ProcessableGraph>() {
-      @Nullable
+    return new MemoizingEvaluator.GraphTransformerForTesting() {
       @Override
-      public ProcessableGraph apply(ThinNodeQueryableGraph queryableGraph) {
-        if (queryableGraph instanceof InMemoryGraph) {
-          return new NotifyingInMemoryGraph((InMemoryGraph) queryableGraph, listener);
-        } else {
-          return new NotifyingGraph<>(queryableGraph, listener);
-        }
+      public InMemoryGraph transform(InMemoryGraph graph) {
+        return new NotifyingInMemoryGraph(graph, listener);
+      }
+
+      @Override
+      public InvalidatableGraph transform(InvalidatableGraph graph) {
+        return new NotifyingInvalidatableGraph(graph, listener);
+      }
+
+      @Override
+      public ProcessableGraph transform(ProcessableGraph graph) {
+        return new NotifyingProcessableGraph(graph, listener);
       }
     };
   }
 
-  protected final TGraph delegate;
-  private final Listener graphListener;
+  protected final Listener graphListener;
 
-  private final EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry> wrapEntry =
+  protected final EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry> wrapEntry =
       new EntryTransformer<SkyKey, ThinNodeEntry, NodeEntry>() {
         @Nullable
         @Override
@@ -66,49 +63,75 @@
         }
       };
 
-  NotifyingGraph(TGraph delegate, Listener graphListener) {
-    this.delegate = delegate;
+  NotifyingHelper(Listener graphListener) {
     this.graphListener = new ErrorRecordingDelegatingListener(graphListener);
   }
 
-  private ProcessableGraph getProcessableDelegate() {
-    return (ProcessableGraph) delegate;
-  }
-
-  @Override
-  public void remove(SkyKey key) {
-    getProcessableDelegate().remove(key);
-  }
-
-  @Override
-  public Map<SkyKey, NodeEntry> createIfAbsentBatch(Iterable<SkyKey> keys) {
-    for (SkyKey key : keys) {
-      graphListener.accept(key, EventType.CREATE_IF_ABSENT, Order.BEFORE, null);
-    }
-    return Maps.transformEntries(getProcessableDelegate().createIfAbsentBatch(keys), wrapEntry);
-  }
-
-  @Override
-  public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
-    if (delegate instanceof ProcessableGraph) {
-      return Maps.transformEntries(getProcessableDelegate().getBatch(keys), wrapEntry);
-    } else {
-      return Maps.transformEntries(delegate.getBatch(keys), wrapEntry);
-    }
-  }
-
-  @Nullable
-  @Override
-  public NodeEntry get(SkyKey key) {
-    return wrapEntry(key, getProcessableDelegate().get(key));
-  }
-
   /** Subclasses should override if they wish to subclass NotifyingNodeEntry. */
   @Nullable
   protected NotifyingNodeEntry wrapEntry(SkyKey key, @Nullable ThinNodeEntry entry) {
     return entry == null ? null : new NotifyingNodeEntry(key, entry);
   }
 
+  static class NotifyingInvalidatableGraph implements InvalidatableGraph {
+    private final InvalidatableGraph delegate;
+    private final NotifyingHelper notifyingHelper;
+
+    NotifyingInvalidatableGraph(InvalidatableGraph delegate, Listener graphListener) {
+      this.notifyingHelper = new NotifyingHelper(graphListener);
+      this.delegate = delegate;
+    }
+
+    NotifyingInvalidatableGraph(InvalidatableGraph delegate, NotifyingHelper helper) {
+      this.notifyingHelper = helper;
+      this.delegate = delegate;
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
+      return Maps.transformEntries(delegate.getBatch(keys), notifyingHelper.wrapEntry);
+    }
+  }
+
+  static class NotifyingProcessableGraph implements ProcessableGraph {
+    protected final ProcessableGraph delegate;
+    protected final NotifyingHelper notifyingHelper;
+
+    NotifyingProcessableGraph(ProcessableGraph delegate, Listener graphListener) {
+      this.notifyingHelper = new NotifyingHelper(graphListener);
+      this.delegate = delegate;
+    }
+
+    NotifyingProcessableGraph(ProcessableGraph delegate, NotifyingHelper helper) {
+      this.notifyingHelper = helper;
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void remove(SkyKey key) {
+      delegate.remove(key);
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> createIfAbsentBatch(Iterable<SkyKey> keys) {
+      for (SkyKey key : keys) {
+        notifyingHelper.graphListener.accept(key, EventType.CREATE_IF_ABSENT, Order.BEFORE, null);
+      }
+      return Maps.transformEntries(delegate.createIfAbsentBatch(keys), notifyingHelper.wrapEntry);
+    }
+
+    @Override
+    public Map<SkyKey, NodeEntry> getBatch(Iterable<SkyKey> keys) {
+      return Maps.transformEntries(delegate.getBatch(keys), notifyingHelper.wrapEntry);
+    }
+
+    @Nullable
+    @Override
+    public NodeEntry get(SkyKey key) {
+      return notifyingHelper.wrapEntry(key, delegate.get(key));
+    }
+  }
+
   /**
    * Graph/value entry events that the receiver can be informed of. When writing tests, feel free to
    * add additional events here if needed.
diff --git a/src/test/java/com/google/devtools/build/skyframe/NotifyingInMemoryGraph.java b/src/test/java/com/google/devtools/build/skyframe/NotifyingInMemoryGraph.java
index 9b90491..752dac7 100644
--- a/src/test/java/com/google/devtools/build/skyframe/NotifyingInMemoryGraph.java
+++ b/src/test/java/com/google/devtools/build/skyframe/NotifyingInMemoryGraph.java
@@ -15,24 +15,25 @@
 
 import java.util.Map;
 
-/** {@link NotifyingGraph} that additionally implements the {@link InMemoryGraph} interface. */
-class NotifyingInMemoryGraph extends NotifyingGraph<InMemoryGraph> implements InMemoryGraph {
-  NotifyingInMemoryGraph(InMemoryGraph delegate, Listener graphListener) {
+/** {@link NotifyingHelper} that additionally implements the {@link InMemoryGraph} interface. */
+class NotifyingInMemoryGraph extends NotifyingHelper.NotifyingProcessableGraph
+    implements InMemoryGraph {
+  NotifyingInMemoryGraph(InMemoryGraph delegate, NotifyingHelper.Listener graphListener) {
     super(delegate, graphListener);
   }
 
   @Override
   public Map<SkyKey, SkyValue> getValues() {
-    return delegate.getValues();
+    return ((InMemoryGraph) delegate).getValues();
   }
 
   @Override
   public Map<SkyKey, SkyValue> getDoneValues() {
-    return delegate.getDoneValues();
+    return ((InMemoryGraph) delegate).getDoneValues();
   }
 
   @Override
   public Map<SkyKey, NodeEntry> getAllValues() {
-    return delegate.getAllValues();
+    return ((InMemoryGraph) delegate).getAllValues();
   }
 }
diff --git a/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java b/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
index 0b3d8fc..56dcfb5 100644
--- a/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/ParallelEvaluatorTest.java
@@ -43,9 +43,9 @@
 import com.google.devtools.build.lib.testutil.TestThread;
 import com.google.devtools.build.lib.testutil.TestUtils;
 import com.google.devtools.build.skyframe.GraphTester.StringValue;
-import com.google.devtools.build.skyframe.NotifyingGraph.EventType;
-import com.google.devtools.build.skyframe.NotifyingGraph.Listener;
-import com.google.devtools.build.skyframe.NotifyingGraph.Order;
+import com.google.devtools.build.skyframe.NotifyingHelper.EventType;
+import com.google.devtools.build.skyframe.NotifyingHelper.Listener;
+import com.google.devtools.build.skyframe.NotifyingHelper.Order;
 import com.google.devtools.build.skyframe.ParallelEvaluator.EventFilter;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 
@@ -1276,7 +1276,7 @@
    * we should detect cycle.
    */
   private void cycleAndErrorInBubbleUp(boolean keepGoing) throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey errorKey = GraphTester.toSkyKey("error");
     SkyKey cycleKey = GraphTester.toSkyKey("cycle");
@@ -1323,7 +1323,7 @@
    */
   @Test
   public void cycleAndErrorAndOtherInBubbleUp() throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey errorKey = GraphTester.toSkyKey("error");
     SkyKey cycleKey = GraphTester.toSkyKey("cycle");
@@ -1366,7 +1366,7 @@
    * Here, we add an additional top-level key in error, just to mix it up.
    */
   private void cycleAndErrorAndError(boolean keepGoing) throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey errorKey = GraphTester.toSkyKey("error");
     SkyKey cycleKey = GraphTester.toSkyKey("cycle");
@@ -1999,7 +1999,7 @@
       }
     });
     graph =
-        new NotifyingGraph<>(
+        new NotifyingHelper.NotifyingProcessableGraph(
             new InMemoryGraphImpl(),
             new Listener() {
               @Override
@@ -2032,7 +2032,7 @@
 
   @Test
   public void cachedErrorsFromKeepGoingUsedOnNoKeepGoing() throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey errorKey = GraphTester.toSkyKey("error");
     SkyKey parent1Key = GraphTester.toSkyKey("parent1");
@@ -2052,7 +2052,7 @@
 
   @Test
   public void cachedTopLevelErrorsShouldHaltNoKeepGoingBuildEarly() throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey errorKey = GraphTester.toSkyKey("error");
     tester.getOrCreate(errorKey).setHasError(true);
@@ -2082,7 +2082,7 @@
 
   private void runUnhandledTransitiveErrors(boolean keepGoing,
       final boolean explicitlyPropagateError) throws Exception {
-    graph = new DeterministicGraph<>(new InMemoryGraphImpl());
+    graph = new DeterministicHelper.DeterministicProcessableGraph(new InMemoryGraphImpl());
     tester = new GraphTester();
     SkyKey grandparentKey = GraphTester.toSkyKey("grandparent");
     final SkyKey parentKey = GraphTester.toSkyKey("parent");