diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 2fc15426457db3..44db96bc3765df 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -364,6 +364,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster", "//src/main/java/com/google/devtools/build/lib/skyframe:target_pattern_phase_value", "//src/main/java/com/google/devtools/build/lib/skyframe:top_down_action_cache", + "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info", "//src/main/java/com/google/devtools/build/lib/unix", "//src/main/java/com/google/devtools/build/lib/util", "//src/main/java/com/google/devtools/build/lib/util:TestType", diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD index 1422386b907da0..3d4ffb0e7479ae 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD +++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD @@ -1196,6 +1196,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/actions:artifacts", "//src/main/java/com/google/devtools/build/lib/collect/nestedset", "//src/main/java/com/google/devtools/build/lib/shell", + "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info", "//src/main/java/com/google/devtools/build/lib/util", "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code", "//src/main/java/com/google/devtools/build/lib/vfs", @@ -1203,6 +1204,7 @@ java_library( "//src/main/java/com/google/devtools/common/options", "//src/main/protobuf:failure_details_java_proto", "//third_party:guava", + "//third_party:jsr305", ], ) diff --git a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java index e22f5040e8b07c..84e5cdb02c9636 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java @@ -26,6 +26,7 @@ import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.server.FailureDetails.WorkspaceStatus; import com.google.devtools.build.lib.server.FailureDetails.WorkspaceStatus.Code; +import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff; import com.google.devtools.build.lib.util.DetailedExitCode; import com.google.devtools.build.lib.util.OptionsUtils; import com.google.devtools.build.lib.vfs.FileSystemUtils; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; /** * An action writing the workspace status files. @@ -174,6 +176,10 @@ public interface Environment { public interface DummyEnvironment { Path getWorkspace(); + /** Returns optional precomputed workspace info to include in the build info event. */ + @Nullable + WorkspaceInfoFromDiff getWorkspaceInfoFromDiff(); + String getBuildRequestId(); OptionsProvider getOptions(); diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java index 5b3c38537589d4..d8b5472bbfae55 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java @@ -55,6 +55,7 @@ import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration; import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor; +import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff; import com.google.devtools.build.lib.skyframe.actiongraph.v2.ActionGraphDump; import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler; import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler.OutputType; @@ -274,6 +275,12 @@ public String getBuildRequestId() { public OptionsProvider getOptions() { return env.getOptions(); } + + @Nullable + @Override + public WorkspaceInfoFromDiff getWorkspaceInfoFromDiff() { + return env.getWorkspaceInfoFromDiff(); + } }))); } } diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java index 90ca40c076177f..d0fa01e817fb6e 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java @@ -14,6 +14,8 @@ package com.google.devtools.build.lib.runtime; +import static com.google.common.base.Preconditions.checkState; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.eventbus.EventBus; @@ -40,6 +42,7 @@ import com.google.devtools.build.lib.skyframe.SkyframeBuildView; import com.google.devtools.build.lib.skyframe.SkyframeExecutor; import com.google.devtools.build.lib.skyframe.TopDownActionCache; +import com.google.devtools.build.lib.skyframe.WorkspaceInfoFromDiff; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.util.DetailedExitCode; import com.google.devtools.build.lib.util.io.OutErr; @@ -100,6 +103,7 @@ public class CommandEnvironment { private TopDownActionCache topDownActionCache; private String workspaceName; private boolean hasSyncedPackageLoading = false; + @Nullable private WorkspaceInfoFromDiff workspaceInfoFromDiff; // This AtomicReference is set to: // - null, if neither BlazeModuleEnvironment#exit nor #precompleteCommand have been called @@ -515,7 +519,7 @@ public String getWorkspaceName() { } public void setWorkspaceName(String workspaceName) { - Preconditions.checkState(this.workspaceName == null, "workspace name can only be set once"); + checkState(this.workspaceName == null, "workspace name can only be set once"); this.workspaceName = workspaceName; eventBus.post(new ExecRootEvent(getExecRoot())); } @@ -579,6 +583,18 @@ public void setOutputServiceForTesting(@Nullable OutputService outputService) { this.outputService = outputService; } + /** + * Returns precomputed workspace information or null. + * + *
Precomputed workspace info is an optimization allowing to share information about the
+ * workspace if it was derived at the time of synchronizing the workspace. This way we can make it
+ * available earlier during the build and avoid retrieving it again.
+ */
+ @Nullable
+ public WorkspaceInfoFromDiff getWorkspaceInfoFromDiff() {
+ return workspaceInfoFromDiff;
+ }
+
public ActionCache getPersistentActionCache() throws IOException {
return workspace.getPersistentActionCache(reporter);
}
@@ -677,16 +693,17 @@ public void syncPackageLoading(OptionsProvider options)
"We should never call this method more than once over the course of a single command");
}
hasSyncedPackageLoading = true;
- getSkyframeExecutor()
- .sync(
- reporter,
- options.getOptions(PackageOptions.class),
- packageLocator,
- options.getOptions(BuildLanguageOptions.class),
- getCommandId(),
- clientEnv,
- timestampGranularityMonitor,
- options);
+ workspaceInfoFromDiff =
+ getSkyframeExecutor()
+ .sync(
+ reporter,
+ options.getOptions(PackageOptions.class),
+ packageLocator,
+ options.getOptions(BuildLanguageOptions.class),
+ getCommandId(),
+ clientEnv,
+ timestampGranularityMonitor,
+ options);
}
public void recordLastExecutionTime() {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index d10bbfced58fb3..c8e8fe6010e2ad 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -213,6 +213,7 @@ java_library(
":tree_artifact_value",
":unloaded_toolchain_context",
":unloaded_toolchain_context_impl",
+ ":workspace_info",
":workspace_name_function",
":workspace_name_value",
":workspace_status_function",
@@ -1278,6 +1279,7 @@ java_library(
deps = [
":broken_diff_awareness_exception",
":incompatible_view_exception",
+ ":workspace_info",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
"//third_party:jsr305",
@@ -1291,6 +1293,7 @@ java_library(
":broken_diff_awareness_exception",
":diff_awareness",
":incompatible_view_exception",
+ ":workspace_info",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
@@ -2767,6 +2770,11 @@ java_library(
],
)
+java_library(
+ name = "workspace_info",
+ srcs = ["WorkspaceInfoFromDiff.java"],
+)
+
java_library(
name = "workspace_name_function",
srcs = ["WorkspaceNameFunction.java"],
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java b/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
index da0ee7d2204365..a1e3ad7dbaf498 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BrokenDiffAwarenessException.java
@@ -13,7 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
-import com.google.common.base.Preconditions;
+import static com.google.common.base.Preconditions.checkNotNull;
/**
* Thrown on {@link DiffAwareness#getDiff} to indicate that something is wrong with the
@@ -22,6 +22,10 @@
public class BrokenDiffAwarenessException extends Exception {
public BrokenDiffAwarenessException(String msg) {
- super(Preconditions.checkNotNull(msg));
+ super(checkNotNull(msg));
+ }
+
+ public BrokenDiffAwarenessException(String msg, Throwable cause) {
+ super(checkNotNull(msg), cause);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
index 0579d9e106a9d8..b05b0704498b92 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwareness.java
@@ -45,6 +45,11 @@ public interface Factory {
/** Opaque view of the filesystem under a package path entry at a specific point in time. */
interface View {
+ /** Returns workspace info unanimously associated with the package path or null. */
+ @Nullable
+ default WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return null;
+ }
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
index ed56b418516fb5..1958441ec7d8eb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManager.java
@@ -69,6 +69,9 @@ public void reset() {
public interface ProcessableModifiedFileSet {
ModifiedFileSet getModifiedFileSet();
+ @Nullable
+ WorkspaceInfoFromDiff getWorkspaceInfo();
+
/**
* This should be called when the changes have been noted. Otherwise, the result from the next
* call to {@link #getDiff} will be from the baseline of the old, unprocessed, diff.
@@ -99,7 +102,7 @@ public ProcessableModifiedFileSet getDiff(
if (baselineView == null) {
logger.atInfo().log("Initial baseline view for %s is %s", pathEntry, newView);
diffAwarenessState.baselineView = newView;
- return BrokenProcessableModifiedFileSet.INSTANCE;
+ return new InitialModifiedFileSet(newView.getWorkspaceInfo());
}
ModifiedFileSet diff;
@@ -172,6 +175,12 @@ public ModifiedFileSet getModifiedFileSet() {
return modifiedFileSet;
}
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return nextView.getWorkspaceInfo();
+ }
+
@Override
public void markProcessed() {
DiffAwarenessState diffAwarenessState = currentDiffAwarenessStates.get(pathEntry);
@@ -191,6 +200,36 @@ public ModifiedFileSet getModifiedFileSet() {
return ModifiedFileSet.EVERYTHING_MODIFIED;
}
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return null;
+ }
+
+ @Override
+ public void markProcessed() {}
+ }
+
+ /** Modified file set for a clean build. */
+ private static class InitialModifiedFileSet implements ProcessableModifiedFileSet {
+
+ @Nullable private final WorkspaceInfoFromDiff workspaceInfo;
+
+ InitialModifiedFileSet(@Nullable WorkspaceInfoFromDiff workspaceInfo) {
+ this.workspaceInfo = workspaceInfo;
+ }
+
+ @Override
+ public ModifiedFileSet getModifiedFileSet() {
+ return ModifiedFileSet.EVERYTHING_MODIFIED;
+ }
+
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return workspaceInfo;
+ }
+
@Override
public void markProcessed() {
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index 88fd0045117e36..83d9c402ee43ad 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -227,8 +227,9 @@ public RecordingDifferencer getDifferencerForTesting() {
return recordingDiffer;
}
+ @Nullable
@Override
- public void sync(
+ public WorkspaceInfoFromDiff sync(
ExtendedEventHandler eventHandler,
PackageOptions packageOptions,
PathPackageLocator packageLocator,
@@ -263,11 +264,13 @@ public void sync(
tsgm,
options);
long startTime = System.nanoTime();
- handleDiffs(eventHandler, packageOptions.checkOutputFiles, options);
+ WorkspaceInfoFromDiff workspaceInfo =
+ handleDiffs(eventHandler, packageOptions.checkOutputFiles, options);
long stopTime = System.nanoTime();
Profiler.instance().logSimpleTask(startTime, stopTime, ProfilerTask.INFO, "handleDiffs");
long duration = stopTime - startTime;
sourceDiffCheckingDuration = duration > 0 ? Duration.ofNanos(duration) : Duration.ZERO;
+ return workspaceInfo;
}
/**
@@ -342,7 +345,8 @@ public void handleDiffsForTesting(ExtendedEventHandler eventHandler)
handleDiffs(eventHandler, /*checkOutputFiles=*/false, OptionsProvider.EMPTY);
}
- private void handleDiffs(
+ @Nullable
+ private WorkspaceInfoFromDiff handleDiffs(
ExtendedEventHandler eventHandler, boolean checkOutputFiles, OptionsProvider options)
throws InterruptedException, AbruptExitException {
TimestampGranularityMonitor tsgm = this.tsgm.get();
@@ -355,13 +359,18 @@ private void handleDiffs(
invalidateCachedWorkspacePathsStates();
}
+ WorkspaceInfoFromDiff workspaceInfo = null;
Map Returns precomputed information about the workspace if it is available at this stage. This
+ * is an optimization allowing implementations which have such information to make it available
+ * early in the build.
*/
- public void sync(
+ @Nullable
+ public WorkspaceInfoFromDiff sync(
ExtendedEventHandler eventHandler,
PackageOptions packageOptions,
PathPackageLocator pathPackageLocator,
@@ -2657,6 +2662,7 @@ public void sync(
dropConfiguredTargetsNow(eventHandler);
lastAnalysisDiscarded = false;
}
+ return null;
}
protected void syncPackageLoading(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java
new file mode 100644
index 00000000000000..c8520d39a4fdf7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java
@@ -0,0 +1,17 @@
+// Copyright 2021 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.lib.skyframe;
+
+/** Information for a workspace computed at the time of collecting diff. */
+public interface WorkspaceInfoFromDiff {}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index 28964d14f77f4f..39fbfc108f47c1 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -86,7 +86,6 @@ java_test(
],
deps = select({
"//src/conditions:darwin": [
- "//src/main/java/com/google/devtools/build/lib/skyframe:incompatible_view_exception",
"//src/main/java/com/google/devtools/build/lib/skyframe:local_diff_awareness",
"//src/main/java/com/google/devtools/build/lib/testing/common:fake-options",
],
@@ -198,6 +197,7 @@ java_test(
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_descriptor",
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_function",
"//src/main/java/com/google/devtools/build/lib/skyframe:glob_value",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:incompatible_view_exception",
"//src/main/java/com/google/devtools/build/lib/skyframe:local_repository_lookup_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:managed_directories_knowledge",
"//src/main/java/com/google/devtools/build/lib/skyframe:output_store",
@@ -230,6 +230,7 @@ java_test(
"//src/main/java/com/google/devtools/build/lib/skyframe:tree_artifact_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:unloaded_toolchain_context",
"//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_context_key",
+ "//src/main/java/com/google/devtools/build/lib/skyframe:workspace_info",
"//src/main/java/com/google/devtools/build/lib/skyframe:workspace_name_value",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
index 8cd2ecb02375f9..853ebe75f9ed75 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/DiffAwarenessManagerTest.java
@@ -15,11 +15,16 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.events.util.EventCollectionApparatus;
+import com.google.devtools.build.lib.skyframe.DiffAwareness.View;
import com.google.devtools.build.lib.skyframe.DiffAwarenessManager.ProcessableModifiedFileSet;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileSystem;
@@ -195,6 +200,135 @@ public void testHandlesBrokenDiffs() throws Exception {
processableDiff.markProcessed();
}
+ @Test
+ public void getDiff_cleanBuild_propagatesWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ when(diffAwareness.getCurrentView(any())).thenReturn(createView(workspaceInfo));
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo);
+ }
+
+ @Test
+ public void getDiff_incrementalBuild_propagatesLatestWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2))
+ .thenReturn(ModifiedFileSet.builder().modify(PathFragment.create("file")).build());
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo2);
+ }
+
+ @Test
+ public void getDiff_incrementalBuildNoChange_propagatesNewWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenReturn(ModifiedFileSet.NOTHING_MODIFIED);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isSameInstanceAs(workspaceInfo2);
+ }
+
+ @Test
+ public void getDiff_incrementalBuildWithNoWorkspaceInfo_returnsDiffWithNullWorkspaceInfo()
+ throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(new WorkspaceInfoFromDiff() {});
+ View view2 = createView(/*workspaceInfo=*/ null);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2))
+ .thenReturn(ModifiedFileSet.builder().modify(PathFragment.create("file")).build());
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isNull();
+ }
+
+ @Test
+ public void getDiff_brokenDiffAwareness_returnsDiffWithNullWorkspaceInfo() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ WorkspaceInfoFromDiff workspaceInfo1 = new WorkspaceInfoFromDiff() {};
+ WorkspaceInfoFromDiff workspaceInfo2 = new WorkspaceInfoFromDiff() {};
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(workspaceInfo1);
+ View view2 = createView(workspaceInfo2);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenThrow(BrokenDiffAwarenessException.class);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ ProcessableModifiedFileSet diff =
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThat(diff.getWorkspaceInfo()).isNull();
+ }
+
+ @Test
+ public void getDiff_incompatibleDiff_fails() throws Exception {
+ Root pathEntry = Root.fromPath(fs.getPath("/path"));
+ DiffAwareness diffAwareness = mock(DiffAwareness.class);
+ View view1 = createView(/*workspaceInfo=*/ null);
+ View view2 = createView(/*workspaceInfo=*/ null);
+ when(diffAwareness.getCurrentView(any())).thenReturn(view1, view2);
+ when(diffAwareness.getDiff(view1, view2)).thenThrow(IncompatibleViewException.class);
+ DiffAwareness.Factory factory = mock(DiffAwareness.Factory.class);
+ when(factory.maybeCreate(pathEntry)).thenReturn(diffAwareness);
+ DiffAwarenessManager manager = new DiffAwarenessManager(ImmutableList.of(factory));
+ manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> manager.getDiff(events.reporter(), pathEntry, OptionsProvider.EMPTY));
+ }
+
+ private static View createView(@Nullable WorkspaceInfoFromDiff workspaceInfo) {
+ return new View() {
+ @Nullable
+ @Override
+ public WorkspaceInfoFromDiff getWorkspaceInfo() {
+ return workspaceInfo;
+ }
+ };
+ }
+
private static class DiffAwarenessFactoryStub implements DiffAwareness.Factory {
private final Map