blob: 512bcc72be80556eb4765eccc16ec71692daf2f8 [file] [log] [blame]
// Copyright 2023 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 static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.devtools.build.skyframe.NodeEntrySubjectFactory.assertThatNodeEntry;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.skyframe.NodeEntry.DependencyState;
import com.google.devtools.build.skyframe.NodeEntry.DirtyType;
import com.google.devtools.build.skyframe.NodeEntry.LifecycleState;
import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link IncrementalInMemoryNodeEntry}. */
@RunWith(TestParameterInjector.class)
public class IncrementalInMemoryNodeEntryTest extends InMemoryNodeEntryTest<IntVersion> {
protected final IntVersion incrementalVersion = initialVersion.next();
@Override
protected IncrementalInMemoryNodeEntry createEntry(SkyKey key) {
return new IncrementalInMemoryNodeEntry(key);
}
@Override
final IntVersion getInitialVersion() {
return IntVersion.of(0);
}
@Test
public void dirtyLifecycle() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
SkyValue oldValue = new IntegerValue(1);
setValue(entry, oldValue, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
assertThat(entry.toValue()).isEqualTo(oldValue);
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
assertThat(entry.getTemporaryDirectDeps()).isEmpty();
assertThat(entry.getTemporaryDirectDeps() instanceof GroupedDeps.WithHashSet)
.isEqualTo(isPartialReevaluation);
SkyKey parent = key("parent");
entry.addReverseDepAndCheckIfDone(parent);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(incrementalVersion, dep);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
entry.markRebuilding();
assertThat(setValue(entry, new IntegerValue(2), /* errorInfo= */ null, incrementalVersion))
.containsExactly(parent);
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
}
@Test
public void changedLifecycle() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
SkyValue oldValue = new IntegerValue(1);
setValue(entry, oldValue, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.CHANGE);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isTrue();
assertThat(entry.isDone()).isFalse();
assertThat(entry.toValue()).isEqualTo(oldValue);
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
SkyKey parent = key("parent");
entry.addReverseDepAndCheckIfDone(parent);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
assertThat(entry.getTemporaryDirectDeps()).isEmpty();
entry.markRebuilding();
assertThat(setValue(entry, new SkyValue() {}, /* errorInfo= */ null, incrementalVersion))
.containsExactly(parent);
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
}
@Test
public void markDirtyThenChanged() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
entry.markDirty(DirtyType.CHANGE);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isTrue();
assertThat(entry.isDone()).isFalse();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
}
@Test
public void markChangedThenDirty() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.CHANGE);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isTrue();
assertThat(entry.isDone()).isFalse();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isTrue();
assertThat(entry.isDone()).isFalse();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
}
@Test
public void crashOnTwiceMarkedChanged() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.CHANGE);
assertThrows(
"Cannot mark entry changed twice",
IllegalStateException.class,
() -> entry.markDirty(DirtyType.CHANGE));
}
@Test
public void crashOnTwiceMarkedDirty() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
entry.markDirty(DirtyType.DIRTY);
assertThrows(
"Cannot mark entry dirty twice",
IllegalStateException.class,
() -> entry.markDirty(DirtyType.DIRTY));
}
@Test
public void forceRebuildAfterTransientError() throws Exception {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
entry.addSingletonTemporaryDirectDep(ErrorTransienceValue.KEY);
entry.signalDep(initialVersion, ErrorTransienceValue.KEY);
setValue(
entry,
/* value= */ null,
ErrorInfo.fromException(
new ReifiedSkyFunctionException(
new GenericFunctionException(
new SomeErrorException("transient error"), Transience.TRANSIENT)),
/* isTransitivelyTransient= */ true),
initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
assertThat(entry.getTemporaryDirectDeps() instanceof GroupedDeps.WithHashSet)
.isEqualTo(isPartialReevaluation);
SkyKey parent = key("parent");
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(parent)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
assertThat(entry.getTemporaryDirectDeps()).isEmpty();
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
assertThat(entry.signalDep(initialVersion, dep)).isTrue();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(ErrorTransienceValue.KEY);
entry.forceRebuild();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.REBUILDING);
assertThat(setValue(entry, new SkyValue() {}, /* errorInfo= */ null, incrementalVersion))
.containsExactly(parent);
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
assertThat(entry.getDirectDeps()).containsExactly(dep); // No more dep on error transience.
}
@Test
public void crashOnAddReverseDepTwiceAfterDone() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
SkyKey parent = key("parent");
assertThat(entry.addReverseDepAndCheckIfDone(parent)).isEqualTo(DependencyState.DONE);
entry.addReverseDepAndCheckIfDone(parent);
assertThrows(
"Cannot add same dep twice",
IllegalStateException.class,
// We only check for duplicates when we request all the reverse deps.
entry::getReverseDepsForDoneEntry);
}
@Test
public void crashOnAddReverseDepBeforeAfterDone() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
SkyKey parent = key("parent");
assertThat(entry.addReverseDepAndCheckIfDone(parent))
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
entry.markRebuilding();
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
entry.addReverseDepAndCheckIfDone(parent);
assertThrows(
"Cannot add same dep twice",
IllegalStateException.class,
// We only check for duplicates when we request all the reverse deps.
entry::getReverseDepsForDoneEntry);
}
@Test
public void pruneBeforeBuild() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
SkyKey dep = key("dep");
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
SkyKey parent = key("parent");
entry.addReverseDepAndCheckIfDone(parent);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.VERIFIED_CLEAN);
assertThat(entry.markClean().getRdepsToSignal()).containsExactly(parent);
assertThat(entry.isDone()).isTrue();
assertThat(entry.getVersion()).isEqualTo(initialVersion);
}
@Test
public void pruneAfterBuild() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(incrementalVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
entry.markRebuilding();
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, incrementalVersion);
assertThat(entry.isDone()).isTrue();
assertThat(entry.getVersion()).isEqualTo(initialVersion);
}
@Test
public void noPruneWhenDetailsChange() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
SkyKey parent = key("parent");
entry.addReverseDepAndCheckIfDone(parent);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(incrementalVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
ReifiedSkyFunctionException exception =
new ReifiedSkyFunctionException(
new GenericFunctionException(new SomeErrorException("oops"), Transience.PERSISTENT));
entry.markRebuilding();
setValue(
entry, new IntegerValue(5), ErrorInfo.fromException(exception, false), incrementalVersion);
assertThat(entry.isDone()).isTrue();
assertWithMessage("Version increments when setValue changes")
.that(entry.getVersion())
.isEqualTo(IntVersion.of(1));
}
@Test
public void pruneWhenDepGroupReordered() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
SkyKey dep1InGroup = key("dep1InGroup");
SkyKey dep2InGroup = key("dep2InGroup");
entry.addSingletonTemporaryDirectDep(dep);
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep1InGroup, dep2InGroup));
entry.signalDep(initialVersion, dep);
entry.signalDep(initialVersion, dep1InGroup);
entry.signalDep(initialVersion, dep2InGroup);
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, initialVersion);
assertThat(entry.isDirty()).isFalse();
assertThat(entry.isDone()).isTrue();
entry.markDirty(DirtyType.DIRTY);
assertThat(entry.isDirty()).isTrue();
assertThat(entry.isChanged()).isFalse();
assertThat(entry.isDone()).isFalse();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.isReadyToEvaluate()).isTrue();
assertThat(entry.hasUnsignaledDeps()).isFalse();
entry.addReverseDepAndCheckIfDone(null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(incrementalVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
entry.markRebuilding();
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep2InGroup, dep1InGroup));
assertThat(entry.signalDep(incrementalVersion, dep2InGroup)).isFalse();
assertThat(entry.signalDep(incrementalVersion, dep1InGroup)).isTrue();
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, incrementalVersion);
assertThat(entry.isDone()).isTrue();
assertWithMessage("Version does not change when dep group reordered")
.that(entry.getVersion())
.isEqualTo(IntVersion.of(0));
}
@Test
public void errorInfoCannotBePruned() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
ReifiedSkyFunctionException exception =
new ReifiedSkyFunctionException(
new GenericFunctionException(new SomeErrorException("oops"), Transience.PERSISTENT));
ErrorInfo errorInfo = ErrorInfo.fromException(exception, false);
setValue(entry, /* value= */ null, errorInfo, initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Restart evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(incrementalVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
entry.markRebuilding();
setValue(entry, /* value= */ null, errorInfo, incrementalVersion);
assertThat(entry.isDone()).isTrue();
// ErrorInfo is treated as a NotComparableSkyValue, so it is not pruned.
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
}
@Test
public void getDependencyGroup() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
SkyKey dep2 = key("dep2");
SkyKey dep3 = key("dep3");
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep, dep2));
entry.addSingletonTemporaryDirectDep(dep3);
entry.signalDep(initialVersion, dep);
entry.signalDep(initialVersion, dep2);
entry.signalDep(initialVersion, dep3);
setValue(entry, /* value= */ new IntegerValue(5), null, initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Restart evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep, dep2);
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep, dep2));
entry.signalDep(initialVersion, /* childForDebugging= */ null);
entry.signalDep(initialVersion, /* childForDebugging= */ null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep3);
}
@Test
public void maintainDependencyGroupAfterRemoval() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
SkyKey dep2 = key("dep2");
SkyKey dep3 = key("dep3");
SkyKey dep4 = key("dep4");
SkyKey dep5 = key("dep5");
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep, dep2, dep3));
entry.addSingletonTemporaryDirectDep(dep4);
entry.addSingletonTemporaryDirectDep(dep5);
entry.signalDep(initialVersion, dep4);
entry.signalDep(initialVersion, dep);
// Oops! Evaluation terminated with an error, but we're going to set this entry's value anyway.
entry.removeUnfinishedDeps(ImmutableSet.of(dep2, dep3, dep5));
ReifiedSkyFunctionException exception =
new ReifiedSkyFunctionException(
new GenericFunctionException(new SomeErrorException("oops"), Transience.PERSISTENT));
setValue(entry, null, ErrorInfo.fromException(exception, false), initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Restart evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep4);
}
@Test
public void pruneWhenDepsChange() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
entry.addSingletonTemporaryDirectDep(dep);
assertThat(entry.signalDep(incrementalVersion, /* childForDebugging= */ null)).isTrue();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
entry.markRebuilding();
entry.addSingletonTemporaryDirectDep(key("dep2"));
assertThat(entry.signalDep(incrementalVersion, /* childForDebugging= */ null)).isTrue();
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, incrementalVersion);
assertThat(entry.isDone()).isTrue();
assertThatNodeEntry(entry).hasVersionThat().isEqualTo(initialVersion);
}
@Test
public void checkDepsOneByOne() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
List<SkyKey> deps = new ArrayList<>();
for (int ii = 0; ii < 10; ii++) {
SkyKey dep = key(Integer.toString(ii));
deps.add(dep);
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
}
setValue(entry, new IntegerValue(5), /* errorInfo= */ null, initialVersion);
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null); // Start new evaluation.
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
for (int ii = 0; ii < 10; ii++) {
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(deps.get(ii));
entry.addSingletonTemporaryDirectDep(deps.get(ii));
assertThat(entry.signalDep(initialVersion, /* childForDebugging= */ null)).isTrue();
if (ii < 9) {
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
} else {
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.VERIFIED_CLEAN);
}
}
}
@Test
public void signalOnlyNewParents() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(key("parent"));
entry.markRebuilding();
setValue(entry, new SkyValue() {}, /* errorInfo= */ null, initialVersion);
entry.markDirty(DirtyType.CHANGE);
SkyKey newParent = key("new parent");
entry.addReverseDepAndCheckIfDone(newParent);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
entry.markRebuilding();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.REBUILDING);
assertThat(setValue(entry, new SkyValue() {}, /* errorInfo= */ null, incrementalVersion))
.containsExactly(newParent);
}
@Test
public void getCompressedDirectDepsForDoneEntry() throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
ImmutableList<ImmutableList<SkyKey>> groupedDirectDeps =
ImmutableList.of(
ImmutableList.of(key("1A")),
ImmutableList.of(key("2A"), key("2B")),
ImmutableList.of(key("3A"), key("3B"), key("3C")),
ImmutableList.of(key("4A"), key("4B"), key("4C"), key("4D")));
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
entry.markRebuilding();
for (ImmutableList<SkyKey> depGroup : groupedDirectDeps) {
entry.addTemporaryDirectDepGroup(depGroup);
for (SkyKey dep : depGroup) {
entry.signalDep(initialVersion, dep);
}
}
entry.setValue(new IntegerValue(42), IntVersion.of(42L), null);
assertThat(GroupedDeps.decompress(entry.getCompressedDirectDepsForDoneEntry()))
.containsExactlyElementsIn(groupedDirectDeps)
.inOrder();
}
@Test
public void hasAtLeastOneDep_true() throws Exception {
SkyKey dep = key("dep");
InMemoryNodeEntry entry = createEntry();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
entry.markRebuilding();
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
entry.setValue(new IntegerValue(1), initialVersion, null);
assertThat(entry.hasAtLeastOneDep()).isTrue();
}
@Test
public void hasAtLeastOneDep_false() throws Exception {
InMemoryNodeEntry entry = createEntry();
assertThatNodeEntry(entry)
.addReverseDepAndCheckIfDone(null)
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
entry.markRebuilding();
entry.addTemporaryDirectDepGroup(ImmutableList.of());
entry.setValue(new IntegerValue(1), initialVersion, null);
assertThat(entry.hasAtLeastOneDep()).isFalse();
}
@Test
public void getAllDirectDepsForIncompleteNode_notYetEvaluating() throws Exception {
InMemoryNodeEntry entry = createEntry();
assertThat(entry.getAllDirectDepsForIncompleteNode()).isEmpty();
}
@Test
public void getAllDirectDepsForIncompleteNode_initialBuild() throws Exception {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null);
entry.markRebuilding();
SkyKey dep1 = key("dep1");
SkyKey dep2 = key("dep2");
entry.addTemporaryDirectDepGroup(ImmutableList.of(dep1, dep2));
assertThat(entry.getAllDirectDepsForIncompleteNode()).containsExactly(dep1, dep2);
}
@Test
public void getAllDirectDepsForIncompleteNode_incrementalBuild() throws Exception {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null);
entry.markRebuilding();
// Dep added on initial build that stays on incremental build.
SkyKey oldAndNewDep = key("oldAndNewDep");
entry.addSingletonTemporaryDirectDep(oldAndNewDep);
entry.signalDep(initialVersion, oldAndNewDep);
// Dep added on initial build that is removed on incremental build.
SkyKey oldDep = key("oldDep");
entry.addSingletonTemporaryDirectDep(oldDep);
entry.signalDep(initialVersion, oldDep);
// Initial build completes.
setValue(entry, new IntegerValue(1), /* errorInfo= */ null, initialVersion);
// Start of incremental build.
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null);
// First dep changed, causes rebuild.
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(oldAndNewDep);
entry.addSingletonTemporaryDirectDep(oldAndNewDep);
entry.signalDep(incrementalVersion, oldAndNewDep);
entry.markRebuilding();
// New dep requested.
SkyKey newDep = key("newDep");
entry.addSingletonTemporaryDirectDep(newDep);
assertThat(entry.getAllDirectDepsForIncompleteNode())
.containsExactly(oldDep, oldAndNewDep, newDep);
}
@Test
public void getAllDirectDepsForIncompleteNode_afterReset() throws Exception {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null);
entry.markRebuilding();
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
entry.resetEvaluationFromScratch();
assertThat(entry.getAllDirectDepsForIncompleteNode()).containsExactly(dep);
}
@Test
public void resetOnDirtyNode(@TestParameter boolean valueChanges) throws Exception {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
// Rdep added on initial build that stays on incremental build.
SkyKey oldAndNewParent = key("oldAndNewParent");
entry.addReverseDepAndCheckIfDone(oldAndNewParent);
// Rdep added on initial build that is removed on incremental build.
SkyKey oldParent = key("oldParent");
entry.addReverseDepAndCheckIfDone(oldParent);
// Dep added on initial build that stays on incremental build.
SkyKey oldAndNewDep = key("oldAndNewDep");
entry.addSingletonTemporaryDirectDep(oldAndNewDep);
entry.signalDep(initialVersion, oldAndNewDep);
// Dep added on initial build that is removed on incremental build.
SkyKey oldDep = key("oldDep");
entry.addSingletonTemporaryDirectDep(oldDep);
entry.signalDep(initialVersion, oldDep);
// Initial build completes.
SkyValue oldValue = new IntegerValue(1);
setValue(entry, oldValue, /* errorInfo= */ null, initialVersion);
// Start of incremental build.
entry.markDirty(DirtyType.DIRTY);
// One rdep added, one rdep stays, one rdep removed.
SkyKey newParent = key("newParent");
entry.addReverseDepAndCheckIfDone(newParent);
entry.checkIfDoneForDirtyReverseDep(oldAndNewParent);
entry.removeReverseDep(oldParent);
// Old dep added before reset, triggers rebuild.
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(oldAndNewDep);
entry.addSingletonTemporaryDirectDep(oldAndNewDep);
entry.signalDep(incrementalVersion, oldAndNewDep);
entry.markRebuilding();
assertThat(entry.getResetDirectDeps()).isEmpty();
// Reset clears temporary direct deps, but preserves the dirty value.
entry.resetEvaluationFromScratch();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.REBUILDING);
assertThat(entry.getTemporaryDirectDeps()).isEmpty();
assertThat(entry.getTemporaryDirectDeps() instanceof GroupedDeps.WithHashSet)
.isEqualTo(isPartialReevaluation);
assertThat(entry.toValue()).isEqualTo(oldValue);
// Add back same dep.
entry.addSingletonTemporaryDirectDep(oldAndNewDep);
assertThat(entry.signalDep(incrementalVersion, oldAndNewDep)).isTrue();
assertThat(entry.getTemporaryDirectDeps()).containsExactly(ImmutableList.of(oldAndNewDep));
// New dep added after reset.
SkyKey newDep = key("newDep");
entry.addSingletonTemporaryDirectDep(newDep);
assertThat(entry.signalDep(incrementalVersion, newDep)).isTrue();
assertThat(entry.getTemporaryDirectDeps())
.containsExactly(ImmutableList.of(oldAndNewDep), ImmutableList.of(newDep));
// Check dep accounting.
assertThat(entry.getResetDirectDeps()).containsExactly(oldAndNewDep);
assertThat(entry.getAllRemainingDirtyDirectDeps()).containsExactly(oldDep);
// Set value and check that new parents will be signaled.
SkyValue newValue = valueChanges ? new IntegerValue(2) : oldValue;
assertThat(setValue(entry, newValue, /* errorInfo= */ null, incrementalVersion))
.containsExactly(oldAndNewParent, newParent);
if (valueChanges) {
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
} else {
assertThat(entry.getVersion()).isEqualTo(initialVersion); // Change pruning.
}
}
@Test
public void rewindOnIncrementalBuild(@TestParameter boolean valueChanges)
throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
// Initial build.
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
SkyValue oldValue = new IntegerValue(1);
setValue(entry, oldValue, /* errorInfo= */ null, initialVersion);
// Rdeps that exhibit various behavior and timing on the incremental build.
SkyKey earlyOldParent = key("earlyOldParent");
SkyKey lateOldParent = key("lateOldParent");
SkyKey cleanParent = key("cleanParent");
SkyKey resetDirtyParent = key("resetDirtyParent");
SkyKey earlyDirtyParent = key("earlyDirtyParent");
SkyKey lateDirtyParent = key("lateDirtyParent");
entry.addReverseDepAndCheckIfDone(earlyOldParent);
entry.addReverseDepAndCheckIfDone(lateOldParent);
entry.addReverseDepAndCheckIfDone(cleanParent);
entry.addReverseDepAndCheckIfDone(resetDirtyParent);
entry.addReverseDepAndCheckIfDone(earlyDirtyParent);
entry.addReverseDepAndCheckIfDone(lateDirtyParent);
// Start of incremental build.
// Rdep removed before rewinding.
entry.removeReverseDep(earlyOldParent);
// Dirty rdep registered before rewinding.
assertThat(entry.checkIfDoneForDirtyReverseDep(earlyDirtyParent))
.isEqualTo(DependencyState.DONE);
// New rdep registered before rewinding.
SkyKey earlyNewParent = key("earlyNewParent");
assertThat(entry.addReverseDepAndCheckIfDone(earlyNewParent)).isEqualTo(DependencyState.DONE);
// Rewinding initiated.
assertThat(entry.checkIfDoneForDirtyReverseDep(resetDirtyParent))
.isEqualTo(DependencyState.DONE);
assertThat(entry.markDirty(DirtyType.REWIND)).isNotNull();
assertThat(entry.toValue()).isEqualTo(oldValue);
// Parent declares dep again after resetting.
assertThat(entry.checkIfDoneForDirtyReverseDep(resetDirtyParent))
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.NEEDS_REBUILDING);
entry.markRebuilding();
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.REBUILDING);
// Rdep removed after rewinding was initiated.
entry.removeReverseDep(lateOldParent);
// Dirty rdep registered after rewinding was initiated.
assertThat(entry.checkIfDoneForDirtyReverseDep(lateDirtyParent))
.isEqualTo(DependencyState.ALREADY_EVALUATING);
// New rdep registered after rewinding was initiated.
SkyKey lateNewParent = key("lateNewParent");
assertThat(entry.addReverseDepAndCheckIfDone(lateNewParent))
.isEqualTo(DependencyState.ALREADY_EVALUATING);
// Rewound evaluation completes. Only parents that are waiting on the node (registered after
// rewinding was initiated) are signalled.
SkyValue newValue = valueChanges ? new IntegerValue(2) : oldValue;
assertThat(setValue(entry, newValue, /* errorInfo= */ null, incrementalVersion))
.containsExactly(resetDirtyParent, lateDirtyParent, lateNewParent);
assertThat(entry.getValue()).isEqualTo(newValue);
if (valueChanges) {
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
} else {
assertThat(entry.getVersion()).isEqualTo(initialVersion); // Change pruning.
}
// Check rdep accounting.
assertThat(entry.getReverseDepsForDoneEntry())
.containsExactly(
cleanParent,
earlyDirtyParent,
earlyNewParent,
resetDirtyParent,
lateDirtyParent,
lateNewParent);
}
@Test
public void rewindOnDirtyNodeIgnored(@TestParameter boolean valueChanges)
throws InterruptedException {
InMemoryNodeEntry entry = createEntry();
entry.addReverseDepAndCheckIfDone(null); // Start evaluation.
entry.markRebuilding();
// Dep added on initial build that stays on incremental build.
SkyKey dep = key("dep");
entry.addSingletonTemporaryDirectDep(dep);
entry.signalDep(initialVersion, dep);
// Initial build completes.
SkyValue oldValue = new IntegerValue(1);
setValue(entry, oldValue, /* errorInfo= */ null, initialVersion);
// Start of incremental build.
entry.markDirty(DirtyType.DIRTY);
entry.addReverseDepAndCheckIfDone(null);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
// Attempt to rewind node while it is dirty. Its lifecycle state does not change.
entry.markDirty(DirtyType.REWIND);
assertThat(entry.getLifecycleState()).isEqualTo(LifecycleState.CHECK_DEPENDENCIES);
// Add back same dep.
entry.addSingletonTemporaryDirectDep(dep);
assertThat(entry.signalDep(incrementalVersion, dep)).isTrue();
entry.markRebuilding();
assertThat(entry.getTemporaryDirectDeps()).containsExactly(ImmutableList.of(dep));
// Set value and check version.
SkyValue newValue = valueChanges ? new IntegerValue(2) : oldValue;
setValue(entry, newValue, /* errorInfo= */ null, incrementalVersion);
if (valueChanges) {
assertThat(entry.getVersion()).isEqualTo(incrementalVersion);
} else {
assertThat(entry.getVersion()).isEqualTo(initialVersion); // Change pruning.
}
}
}