From 14abe4fd7c3967686a3536939fdc3882e691bca2 Mon Sep 17 00:00:00 2001 From: ajurkowski Date: Mon, 1 Feb 2021 18:39:02 -0800 Subject: [PATCH] Allow `DiffAwareness` to share precomputed information about the workspace and propagate it to the `WorkspaceStatusAction`. Each successful Bazel build issue 2 `BuildInfo` events -- one based on the result of running the command indicated by `workspace_status_command` and a dummy one based on a subset of available information. Receivers of those discard all but the first one. If a build fails before execution phase, the dummy event will be the only even issued, hence it will be used. `DiffAwareness` operates on the workspace to figure out the diffs and in the process probes properties of it. Allow `DiffAwareness` to share such information with the intention to make it available for the rest of the build. Propagate unanimous precomputed workspace information when it is available from `DiffAwareness` through the `DiffAwarenessManager` and `SkyframeExecutor` to `CommandEnvironment` and store it there to make it available for the rest of the build. Make precomputed workspace information available to the dummy `BuildInfo` event. PiperOrigin-RevId: 355072552 --- .../java/com/google/devtools/build/lib/BUILD | 1 + .../google/devtools/build/lib/analysis/BUILD | 2 + .../lib/analysis/WorkspaceStatusAction.java | 6 + .../build/lib/buildtool/BuildTool.java | 7 + .../build/lib/runtime/CommandEnvironment.java | 39 +++-- .../google/devtools/build/lib/skyframe/BUILD | 8 ++ .../BrokenDiffAwarenessException.java | 8 +- .../build/lib/skyframe/DiffAwareness.java | 5 + .../lib/skyframe/DiffAwarenessManager.java | 41 +++++- .../skyframe/SequencedSkyframeExecutor.java | 18 ++- .../build/lib/skyframe/SkyframeExecutor.java | 8 +- .../lib/skyframe/WorkspaceInfoFromDiff.java | 17 +++ .../google/devtools/build/lib/skyframe/BUILD | 3 +- .../skyframe/DiffAwarenessManagerTest.java | 134 ++++++++++++++++++ 14 files changed, 277 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceInfoFromDiff.java 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 modifiedFilesByPathEntry = Maps.newHashMap(); Set> pathEntriesWithoutDiffInformation = Sets.newHashSet(); - for (Root pathEntry : pkgLocator.get().getPathEntries()) { + ImmutableList pkgRoots = pkgLocator.get().getPathEntries(); + for (Root pathEntry : pkgRoots) { DiffAwarenessManager.ProcessableModifiedFileSet modifiedFileSet = diffAwarenessManager.getDiff(eventHandler, pathEntry, options); + if (pkgRoots.size() == 1) { + workspaceInfo = modifiedFileSet.getWorkspaceInfo(); + } if (modifiedFileSet.getModifiedFileSet().treatEverythingAsModified()) { pathEntriesWithoutDiffInformation.add(Pair.of(pathEntry, modifiedFileSet)); } else { @@ -382,6 +391,7 @@ private void handleDiffs( managedDirectoriesChanged, fsvcThreads); handleClientEnvironmentChanges(); + return workspaceInfo; } /** diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java index 2c08e91989d3ce..70bb0f43aaf725 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java @@ -2630,8 +2630,13 @@ ActionExecutionStatusReporter getActionExecutionStatusReporterForTesting() { /** * Initializes and syncs the graph with the given options, readying it for the next evaluation. + * + *

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 diffAwarenesses = Maps.newHashMap();