use of com.google.devtools.build.skyframe.GraphTester.StringValue in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method cycleAndSelfEdgeWithDirtyValue.
/** Regression test: "crash in cycle checker with dirty values". */
@Test
public void cycleAndSelfEdgeWithDirtyValue() throws Exception {
initializeTester();
// The cycle detection algorithm non-deterministically traverses into children nodes, so
// use explicit determinism.
makeGraphDeterministic();
SkyKey cycleKey1 = GraphTester.toSkyKey("ZcycleKey1");
SkyKey cycleKey2 = GraphTester.toSkyKey("AcycleKey2");
tester.getOrCreate(cycleKey1).addDependency(cycleKey2).addDependency(cycleKey1).setComputedValue(CONCATENATE);
tester.getOrCreate(cycleKey2).addDependency(cycleKey1).setComputedValue(COPY);
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
true, cycleKey1);
assertEquals(null, result.get(cycleKey1));
ErrorInfo errorInfo = result.getError(cycleKey1);
CycleInfo cycleInfo = Iterables.getOnlyElement(errorInfo.getCycleInfo());
if (cyclesDetected()) {
assertThat(cycleInfo.getCycle()).containsExactly(cycleKey1).inOrder();
assertThat(cycleInfo.getPathToCycle()).isEmpty();
}
tester.getOrCreate(cycleKey1, /*markAsModified=*/
true);
tester.invalidate();
result = tester.eval(/*keepGoing=*/
true, cycleKey1, cycleKey2);
assertEquals(null, result.get(cycleKey1));
errorInfo = result.getError(cycleKey1);
cycleInfo = Iterables.getOnlyElement(errorInfo.getCycleInfo());
if (cyclesDetected()) {
assertThat(cycleInfo.getCycle()).containsExactly(cycleKey1).inOrder();
assertThat(cycleInfo.getPathToCycle()).isEmpty();
}
cycleInfo = Iterables.getOnlyElement(tester.driver.getExistingErrorForTesting(cycleKey2).getCycleInfo());
if (cyclesDetected()) {
assertThat(cycleInfo.getCycle()).containsExactly(cycleKey1).inOrder();
assertThat(cycleInfo.getPathToCycle()).containsExactly(cycleKey2).inOrder();
}
}
use of com.google.devtools.build.skyframe.GraphTester.StringValue in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method shutDownBuildOnCachedError_Verified.
/**
* Test that the invalidated parent of a cached but invalidated error doesn't get marked clean.
* First build the parent -- it will contain an error. Then invalidate the error via a dependency
* (so it will not actually change) and then build the parent and another node that depends on the
* error. The other node will wait to throw until the parent is signaled that all of its
* dependencies are done, or until it is interrupted. If it throws, the parent will be
* VERIFIED_CLEAN but not done, which is not a valid state once evaluation shuts down. The
* evaluator avoids this situation by throwing when the error is encountered, even though the
* error isn't evaluated or requested by an evaluating node.
*/
@Test
public void shutDownBuildOnCachedError_Verified() throws Exception {
// TrackingProgressReceiver does unnecessary examination of node values.
initializeTester(new TrackingProgressReceiver() {
@Override
public void evaluated(SkyKey skyKey, Supplier<SkyValue> skyValueSupplier, EvaluationState state) {
evaluated.add(skyKey);
}
});
// errorKey will be invalidated due to its dependence on invalidatedKey, but later revalidated
// since invalidatedKey re-evaluates to the same value on a subsequent build.
SkyKey errorKey = GraphTester.toSkyKey("error");
SkyKey invalidatedKey = GraphTester.toSkyKey("invalidated-leaf");
SkyKey changedKey = GraphTester.toSkyKey("changed-leaf");
tester.set(invalidatedKey, new StringValue("invalidated-leaf-value"));
tester.set(changedKey, new StringValue("changed-leaf-value"));
// Names are alphabetized in reverse deps of errorKey.
final SkyKey cachedParentKey = GraphTester.toSkyKey("A-cached-parent");
final SkyKey uncachedParentKey = GraphTester.toSkyKey("B-uncached-parent");
tester.getOrCreate(errorKey).addDependency(invalidatedKey).setHasError(true);
tester.getOrCreate(cachedParentKey).addDependency(errorKey).setComputedValue(CONCATENATE);
tester.getOrCreate(uncachedParentKey).addDependency(changedKey).addDependency(errorKey).setComputedValue(CONCATENATE);
// We only want to force a particular order of operations at some points during evaluation. In
// particular, we don't want to force anything during error bubbling.
final AtomicBoolean synchronizeThreads = new AtomicBoolean(false);
final CountDownLatch shutdownAwaiterStarted = new CountDownLatch(1);
injectGraphListenerForTesting(new Listener() {
private final CountDownLatch cachedSignaled = new CountDownLatch(1);
@Override
public void accept(SkyKey key, EventType type, Order order, Object context) {
if (!synchronizeThreads.get() || order != Order.BEFORE || type != EventType.SIGNAL) {
return;
}
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(shutdownAwaiterStarted, "shutdown awaiter not started");
if (key.equals(uncachedParentKey)) {
// we wait until the cached parent is signaled too.
try {
assertTrue(cachedSignaled.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
} catch (InterruptedException e) {
// Before the relevant bug was fixed, this code was not interrupted, and the
// uncached parent got to build, yielding an inconsistent state at a later point
// during evaluation. With the bugfix, the cached parent is never signaled
// before the evaluator shuts down, and so the above code is interrupted.
Thread.currentThread().interrupt();
}
} else if (key.equals(cachedParentKey)) {
// This branch should never be reached by a well-behaved evaluator, since when the
// error node is reached, the evaluator should shut down. However, we don't test
// for that behavior here because that would be brittle and we expect that such an
// evaluator will crash hard later on in any case.
cachedSignaled.countDown();
try {
// Sleep until we're interrupted by the evaluator, so we know it's shutting
// down.
Thread.sleep(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
Thread currentThread = Thread.currentThread();
throw new IllegalStateException("no interruption in time in " + key + " for " + (currentThread.isInterrupted() ? "" : "un") + "interrupted " + currentThread + " with hash " + System.identityHashCode(currentThread) + " at " + System.currentTimeMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, /*deterministic=*/
true);
// Initialize graph.
tester.eval(/*keepGoing=*/
true, cachedParentKey, uncachedParentKey);
tester.getOrCreate(invalidatedKey, /*markAsModified=*/
true);
tester.set(changedKey, new StringValue("new value"));
tester.invalidate();
synchronizeThreads.set(true);
SkyKey waitForShutdownKey = GraphTester.skyKey("wait-for-shutdown");
tester.getOrCreate(waitForShutdownKey).setBuilder(new SkyFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {
shutdownAwaiterStarted.countDown();
TrackingAwaiter.INSTANCE.awaitLatchAndTrackExceptions(((SkyFunctionEnvironment) env).getExceptionLatchForTesting(), "exception not thrown");
// Threadpool is shutting down. Don't try to synchronize anything in the future
// during error bubbling.
synchronizeThreads.set(false);
throw new InterruptedException();
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
});
EvaluationResult<StringValue> result = tester.eval(/*keepGoing=*/
false, cachedParentKey, uncachedParentKey, waitForShutdownKey);
assertWithMessage(result.toString()).that(result.hasError()).isTrue();
tester.getOrCreate(invalidatedKey, /*markAsModified=*/
true);
tester.invalidate();
result = tester.eval(/*keepGoing=*/
false, cachedParentKey, uncachedParentKey);
assertWithMessage(result.toString()).that(result.hasError()).isTrue();
}
use of com.google.devtools.build.skyframe.GraphTester.StringValue in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method sameDepInTwoGroups.
/**
* The same dep is requested in two groups, but its value determines what the other dep in the
* second group is. When it changes, the other dep in the second group should not be requested.
*/
@Test
public void sameDepInTwoGroups() throws Exception {
initializeTester();
// leaf4 should not built in the second build.
final SkyKey leaf4 = GraphTester.toSkyKey("leaf4");
final AtomicBoolean shouldNotBuildLeaf4 = new AtomicBoolean(false);
injectGraphListenerForTesting(new Listener() {
@Override
public void accept(SkyKey key, EventType type, Order order, Object context) {
if (shouldNotBuildLeaf4.get() && key.equals(leaf4) && type != EventType.REMOVE_REVERSE_DEP) {
throw new IllegalStateException("leaf4 should not have been considered this build: " + type + ", " + order + ", " + context);
}
}
}, /*deterministic=*/
false);
tester.set(leaf4, new StringValue("leaf4"));
// Create leaf0, leaf1 and leaf2 values with values "leaf2", "leaf3", "leaf4" respectively.
// These will be requested as one dependency group. In the second build, leaf2 will have the
// value "leaf5".
final List<SkyKey> leaves = new ArrayList<>();
for (int i = 0; i <= 2; i++) {
SkyKey leaf = GraphTester.toSkyKey("leaf" + i);
leaves.add(leaf);
tester.set(leaf, new StringValue("leaf" + (i + 2)));
}
// Create "top" value. It depends on all leaf values in two overlapping dependency groups.
SkyKey topKey = GraphTester.toSkyKey("top");
final SkyValue topValue = new StringValue("top");
tester.getOrCreate(topKey).setBuilder(new NoExtractorFunction() {
@Override
public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException, InterruptedException {
// Request the first group, [leaf0, leaf1, leaf2].
// In the first build, it has values ["leaf2", "leaf3", "leaf4"].
// In the second build it has values ["leaf2", "leaf3", "leaf5"]
Map<SkyKey, SkyValue> values = env.getValues(leaves);
if (env.valuesMissing()) {
return null;
}
// Request the second group. In the first build it's [leaf2, leaf4].
// In the second build it's [leaf2, leaf5]
env.getValues(ImmutableList.of(leaves.get(2), GraphTester.toSkyKey(((StringValue) values.get(leaves.get(2))).getValue())));
if (env.valuesMissing()) {
return null;
}
return topValue;
}
});
// First build: assert we can evaluate "top".
assertEquals(topValue, tester.evalAndGet(/*keepGoing=*/
false, topKey));
// Second build: replace "leaf4" by "leaf5" in leaf2's value. Assert leaf4 is not requested.
final SkyKey leaf5 = GraphTester.toSkyKey("leaf5");
tester.set(leaf5, new StringValue("leaf5"));
tester.set(leaves.get(2), new StringValue("leaf5"));
tester.invalidate();
shouldNotBuildLeaf4.set(true);
assertEquals(topValue, tester.evalAndGet(/*keepGoing=*/
false, topKey));
}
use of com.google.devtools.build.skyframe.GraphTester.StringValue in project bazel by bazelbuild.
the class EagerInvalidatorTest method assertValueValue.
protected void assertValueValue(String name, String expectedValue) throws InterruptedException {
StringValue value = (StringValue) eval(false, skyKey(name));
assertEquals(expectedValue, value.getValue());
}
use of com.google.devtools.build.skyframe.GraphTester.StringValue in project bazel by bazelbuild.
the class MemoizingEvaluatorTest method deletingDirtyNodes.
/**
* Regression test for case where the user requests that we delete nodes that are already in the
* queue to be dirtied. We should handle that gracefully and not complain.
*/
@Test
public void deletingDirtyNodes() throws Exception {
final Thread thread = Thread.currentThread();
final AtomicBoolean interruptInvalidation = new AtomicBoolean(false);
initializeTester(new TrackingProgressReceiver() {
private final AtomicBoolean firstInvalidation = new AtomicBoolean(true);
@Override
public void invalidated(SkyKey skyKey, InvalidationState state) {
if (interruptInvalidation.get() && !firstInvalidation.getAndSet(false)) {
thread.interrupt();
}
super.invalidated(skyKey, state);
}
});
SkyKey key = null;
// be dirtied will enqueue its parent for dirtying, so it will be in the queue for the next run.
for (int i = 0; i < TEST_NODE_COUNT; i++) {
key = GraphTester.toSkyKey("node" + i);
if (i > 0) {
tester.getOrCreate(key).addDependency("node" + (i - 1)).setComputedValue(COPY);
} else {
tester.set(key, new StringValue("node0"));
}
}
// Seed the graph.
assertEquals("node0", ((StringValue) tester.evalAndGet(/*keepGoing=*/
false, key)).getValue());
// Start the dirtying process.
tester.set("node0", new StringValue("new"));
tester.invalidate();
interruptInvalidation.set(true);
try {
tester.eval(/*keepGoing=*/
false, key);
fail();
} catch (InterruptedException e) {
// Expected.
}
interruptInvalidation.set(false);
// Now delete all the nodes. The node that was going to be dirtied is also deleted, which we
// should handle.
tester.evaluator.delete(Predicates.<SkyKey>alwaysTrue());
assertEquals("new", ((StringValue) tester.evalAndGet(/*keepGoing=*/
false, key)).getValue());
}
Aggregations