Update from Google.

--
MOE_MIGRATED_REVID=85702957
diff --git a/src/test/java/com/google/devtools/build/skyframe/GraphTester.java b/src/test/java/com/google/devtools/build/skyframe/GraphTester.java
new file mode 100644
index 0000000..6095bb8
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/skyframe/GraphTester.java
@@ -0,0 +1,340 @@
+// Copyright 2014 Google Inc. 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.Preconditions;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A helper class to create graphs and run skyframe tests over these graphs.
+ *
+ * <p>There are two types of values, computing values, which may not be set to a constant value,
+ * and leaf values, which must be set to a constant value and may not have any dependencies.
+ *
+ * <p>Note that the value builder looks into the test values created here to determine how to
+ * behave. However, skyframe will only re-evaluate the value and call the value builder if any of
+ * its dependencies has changed. That means in order to change the set of dependencies of a value,
+ * you need to also change one of its previous dependencies to force re-evaluation. Changing a
+ * computing value does not mark it as modified.
+ */
+public class GraphTester {
+
+  // TODO(bazel-team): Split this for computing and non-computing values?
+  public static final SkyFunctionName NODE_TYPE = new SkyFunctionName("Type", false);
+
+  private final Map<SkyKey, TestFunction> values = new HashMap<>();
+  private final Set<SkyKey> modifiedValues = new LinkedHashSet<>();
+
+  public TestFunction getOrCreate(String name) {
+    return getOrCreate(skyKey(name));
+  }
+
+  public TestFunction getOrCreate(SkyKey key) {
+    return getOrCreate(key, false);
+  }
+
+  public TestFunction getOrCreate(SkyKey key, boolean markAsModified) {
+    TestFunction result = values.get(key);
+    if (result == null) {
+      result = new TestFunction();
+      values.put(key, result);
+    } else if (markAsModified) {
+      modifiedValues.add(key);
+    }
+    return result;
+  }
+
+  public TestFunction set(String key, SkyValue value) {
+    return set(skyKey(key), value);
+  }
+
+  public TestFunction set(SkyKey key, SkyValue value) {
+    return getOrCreate(key, true).setConstantValue(value);
+  }
+
+  public Collection<SkyKey> getModifiedValues() {
+    return modifiedValues;
+  }
+
+  public SkyFunction getFunction() {
+    return new SkyFunction() {
+      @Override
+      public SkyValue compute(SkyKey key, Environment env)
+          throws SkyFunctionException, InterruptedException {
+        TestFunction builder = values.get(key);
+        Preconditions.checkState(builder != null, "No TestFunction for " + key);
+        if (builder.builder != null) {
+          return builder.builder.compute(key, env);
+        }
+        if (builder.warning != null) {
+          env.getListener().handle(Event.warn(builder.warning));
+        }
+        if (builder.progress != null) {
+          env.getListener().handle(Event.progress(builder.progress));
+        }
+        Map<SkyKey, SkyValue> deps = new LinkedHashMap<>();
+        boolean oneMissing = false;
+        for (Pair<SkyKey, SkyValue> dep : builder.deps) {
+          SkyValue value;
+          if (dep.second == null) {
+            value = env.getValue(dep.first);
+          } else {
+            try {
+              value = env.getValueOrThrow(dep.first, SomeErrorException.class);
+            } catch (SomeErrorException e) {
+              value = dep.second;
+            }
+          }
+          if (value == null) {
+            oneMissing = true;
+          } else {
+            deps.put(dep.first, value);
+          }
+          Preconditions.checkState(oneMissing == env.valuesMissing());
+        }
+        if (env.valuesMissing()) {
+          return null;
+        }
+
+        if (builder.hasTransientError) {
+          throw new GenericFunctionException(new SomeErrorException(key.toString()),
+              Transience.TRANSIENT);
+        }
+        if (builder.hasError) {
+          throw new GenericFunctionException(new SomeErrorException(key.toString()),
+              Transience.PERSISTENT);
+        }
+
+        if (builder.value != null) {
+          return builder.value;
+        }
+
+        if (Thread.currentThread().isInterrupted()) {
+          throw new InterruptedException(key.toString());
+        }
+
+        return builder.computer.compute(deps, env);
+      }
+
+      @Nullable
+      @Override
+      public String extractTag(SkyKey skyKey) {
+        return values.get(skyKey).tag;
+      }
+    };
+  }
+
+  public static SkyKey skyKey(String key) {
+    return new SkyKey(NODE_TYPE, key);
+  }
+
+  /**
+   * A value in the testing graph that is constructed in the tester.
+   */
+  public class TestFunction {
+    // TODO(bazel-team): We could use a multiset here to simulate multi-pass dependency discovery.
+    private final Set<Pair<SkyKey, SkyValue>> deps = new LinkedHashSet<>();
+    private SkyValue value;
+    private ValueComputer computer;
+    private SkyFunction builder = null;
+
+    private boolean hasTransientError;
+    private boolean hasError;
+
+    private String warning;
+    private String progress;
+
+    private String tag;
+
+    public TestFunction addDependency(String name) {
+      return addDependency(skyKey(name));
+    }
+
+    public TestFunction addDependency(SkyKey key) {
+      deps.add(Pair.<SkyKey, SkyValue>of(key, null));
+      return this;
+    }
+
+    public TestFunction removeDependency(String name) {
+      return removeDependency(skyKey(name));
+    }
+
+    public TestFunction removeDependency(SkyKey key) {
+      deps.remove(Pair.<SkyKey, SkyValue>of(key, null));
+      return this;
+    }
+
+    public TestFunction addErrorDependency(String name, SkyValue altValue) {
+      return addErrorDependency(skyKey(name), altValue);
+    }
+
+    public TestFunction addErrorDependency(SkyKey key, SkyValue altValue) {
+      deps.add(Pair.of(key, altValue));
+      return this;
+    }
+
+    public TestFunction setConstantValue(SkyValue value) {
+      Preconditions.checkState(this.computer == null);
+      this.value = value;
+      return this;
+    }
+
+    public TestFunction setComputedValue(ValueComputer computer) {
+      Preconditions.checkState(this.value == null);
+      this.computer = computer;
+      return this;
+    }
+
+    public TestFunction setBuilder(SkyFunction builder) {
+      Preconditions.checkState(this.value == null);
+      Preconditions.checkState(this.computer == null);
+      Preconditions.checkState(deps.isEmpty());
+      Preconditions.checkState(!hasTransientError);
+      Preconditions.checkState(!hasError);
+      Preconditions.checkState(warning == null);
+      Preconditions.checkState(progress == null);
+      this.builder = builder;
+      return this;
+    }
+
+    public TestFunction setHasTransientError(boolean hasError) {
+      this.hasTransientError = hasError;
+      return this;
+    }
+
+    public TestFunction setHasError(boolean hasError) {
+      // TODO(bazel-team): switch to an enum for hasError.
+      this.hasError = hasError;
+      return this;
+    }
+
+    public TestFunction setWarning(String warning) {
+      this.warning = warning;
+      return this;
+    }
+
+    public TestFunction setProgress(String info) {
+      this.progress = info;
+      return this;
+    }
+
+    public TestFunction setTag(String tag) {
+      this.tag = tag;
+      return this;
+    }
+
+  }
+
+  public static SkyKey[] toSkyKeys(String... names) {
+    SkyKey[] result = new SkyKey[names.length];
+    for (int i = 0; i < names.length; i++) {
+      result[i] = new SkyKey(GraphTester.NODE_TYPE, names[i]);
+    }
+    return result;
+  }
+
+  public static SkyKey toSkyKey(String name) {
+    return toSkyKeys(name)[0];
+  }
+
+  private class DelegatingFunction implements SkyFunction {
+    @Override
+    public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException,
+        InterruptedException {
+      return getFunction().compute(skyKey, env);
+    }
+
+    @Nullable
+    @Override
+    public String extractTag(SkyKey skyKey) {
+      return getFunction().extractTag(skyKey);
+    }
+  }
+
+  public DelegatingFunction createDelegatingFunction() {
+    return new DelegatingFunction();
+  }
+
+  /**
+   * Simple value class that stores strings.
+   */
+  public static class StringValue implements SkyValue {
+    private final String value;
+
+    public StringValue(String value) {
+      this.value = value;
+    }
+
+    public String getValue() {
+      return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof StringValue)) {
+        return false;
+      }
+      return value.equals(((StringValue) o).value);
+    }
+
+    @Override
+    public int hashCode() {
+      return value.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "StringValue: " + getValue();
+    }
+  }
+
+  /**
+   * A callback interface to provide the value computation.
+   */
+  public interface ValueComputer {
+    /** This is called when all the declared dependencies exist. It may request new dependencies. */
+    SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env)
+        throws InterruptedException;
+  }
+
+  public static final ValueComputer COPY = new ValueComputer() {
+    @Override
+    public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
+      return Iterables.getOnlyElement(deps.values());
+    }
+  };
+
+  public static final ValueComputer CONCATENATE = new ValueComputer() {
+    @Override
+    public SkyValue compute(Map<SkyKey, SkyValue> deps, SkyFunction.Environment env) {
+      StringBuilder result = new StringBuilder();
+      for (SkyValue value : deps.values()) {
+        result.append(((StringValue) value).value);
+      }
+      return new StringValue(result.toString());
+    }
+  };
+}