| // Copyright 2022 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; | 
 |  | 
 | import static com.google.common.truth.Truth.assertThat; | 
 | import static org.junit.Assert.assertThrows; | 
 | import static org.mockito.Mockito.mock; | 
 | import static org.mockito.Mockito.when; | 
 |  | 
 | import com.google.common.collect.ImmutableList; | 
 | import com.google.common.collect.Iterables; | 
 | import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; | 
 | import com.google.devtools.build.lib.actions.ActionKeyContext; | 
 | import com.google.devtools.build.lib.actions.ActionLookupKey; | 
 | import com.google.devtools.build.lib.actions.ActionLookupValue; | 
 | import com.google.devtools.build.lib.actions.Artifact; | 
 | import com.google.devtools.build.lib.actions.Artifact.DerivedArtifact; | 
 | import com.google.devtools.build.lib.actions.Artifact.SourceArtifact; | 
 | import com.google.devtools.build.lib.actions.ArtifactPrefixConflictException; | 
 | import com.google.devtools.build.lib.actions.ArtifactRoot; | 
 | import com.google.devtools.build.lib.actions.ArtifactRoot.RootType; | 
 | import com.google.devtools.build.lib.actions.MapBasedActionGraph; | 
 | import com.google.devtools.build.lib.actions.MutableActionGraph; | 
 | import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; | 
 | import com.google.devtools.build.lib.actions.util.TestAction.DummyAction; | 
 | import com.google.devtools.build.lib.concurrent.Sharder; | 
 | import com.google.devtools.build.lib.skyframe.ArtifactConflictFinder.ActionConflictsAndStats; | 
 | import com.google.devtools.build.lib.testutil.Scratch; | 
 | import com.google.devtools.build.lib.vfs.PathFragment; | 
 | import com.google.devtools.build.lib.vfs.Root; | 
 | import java.util.concurrent.ExecutorService; | 
 | import java.util.concurrent.Executors; | 
 | import java.util.concurrent.Future; | 
 | import org.junit.Test; | 
 | import org.junit.runner.RunWith; | 
 | import org.junit.runners.JUnit4; | 
 |  | 
 | /** Unit tests for {@link IncrementalArtifactConflictFinder}. */ | 
 | @RunWith(JUnit4.class) | 
 | public class IncrementalArtifactConflictFinderTest { | 
 |   private final Scratch scratch = new Scratch(); | 
 |  | 
 |   @Test | 
 |   public void testFindArtifactConflicts_sequential() throws Exception { | 
 |  | 
 |     ActionAnalysisMetadata action1 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/a")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/conflicting"))); | 
 |     ActionLookupValue alv1 = createMockActionLookupValueThatContains(action1); | 
 |     Sharder<ActionLookupValue> actionLookupValues1 = createALVSharderOf(alv1); | 
 |  | 
 |     ActionAnalysisMetadata action2 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/b")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/conflicting"))); | 
 |     ActionLookupValue alv2 = createMockActionLookupValueThatContains(action2); | 
 |     Sharder<ActionLookupValue> actionLookupValues2 = createALVSharderOf(alv2); | 
 |  | 
 |     MutableActionGraph actionGraph = new MapBasedActionGraph(new ActionKeyContext()); | 
 |     IncrementalArtifactConflictFinder conflictFinder = | 
 |         IncrementalArtifactConflictFinder.createWithActionGraph(actionGraph); | 
 |  | 
 |     ActionConflictsAndStats expectNoConflict = | 
 |         conflictFinder.findArtifactConflicts(actionLookupValues1, /*strictConflictChecks=*/ true); | 
 |     assertThat(expectNoConflict.getConflicts()).isEmpty(); | 
 |  | 
 |     ActionConflictsAndStats expectConflict = | 
 |         conflictFinder.findArtifactConflicts(actionLookupValues2, /*strictConflictChecks=*/ true); | 
 |     assertThat(expectConflict.getConflicts()).hasSize(1); | 
 |     assertThrows( | 
 |         ActionConflictException.class, | 
 |         () -> expectConflict.getConflicts().get(action2).rethrowTyped()); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void testFindArtifactConflicts_multiThreaded() throws Exception { | 
 |     ActionAnalysisMetadata action1 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/a")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/conflicting"))); | 
 |     ActionLookupValue alv1 = createMockActionLookupValueThatContains(action1); | 
 |     Sharder<ActionLookupValue> actionLookupValues1 = createALVSharderOf(alv1); | 
 |  | 
 |     ActionAnalysisMetadata action2 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/b")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/conflicting"))); | 
 |     ActionLookupValue alv2 = createMockActionLookupValueThatContains(action2); | 
 |     Sharder<ActionLookupValue> actionLookupValues2 = createALVSharderOf(alv2); | 
 |  | 
 |     MutableActionGraph actionGraph = new MapBasedActionGraph(new ActionKeyContext()); | 
 |     IncrementalArtifactConflictFinder conflictFinder = | 
 |         IncrementalArtifactConflictFinder.createWithActionGraph(actionGraph); | 
 |  | 
 |     ExecutorService executor = Executors.newFixedThreadPool(2); | 
 |     Future<ActionConflictsAndStats> future1 = | 
 |         executor.submit( | 
 |             () -> | 
 |                 conflictFinder.findArtifactConflicts( | 
 |                     actionLookupValues1, /*strictConflictChecks=*/ true)); | 
 |     Future<ActionConflictsAndStats> future2 = | 
 |         executor.submit( | 
 |             () -> | 
 |                 conflictFinder.findArtifactConflicts( | 
 |                     actionLookupValues2, /*strictConflictChecks=*/ true)); | 
 |     ActionConflictsAndStats expectNoConflict; | 
 |     ActionConflictsAndStats expectConflict; | 
 |     if (future1.get().getConflicts().isEmpty()) { | 
 |       expectConflict = future2.get(); | 
 |       expectNoConflict = future1.get(); | 
 |     } else { | 
 |       expectConflict = future1.get(); | 
 |       expectNoConflict = future2.get(); | 
 |     } | 
 |     executor.shutdownNow(); | 
 |  | 
 |     assertThat(expectNoConflict.getConflicts()).isEmpty(); | 
 |     assertThat(expectConflict.getConflicts()).hasSize(1); | 
 |     assertThrows( | 
 |         ActionConflictException.class, | 
 |         () -> Iterables.getOnlyElement(expectConflict.getConflicts().values()).rethrowTyped()); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void testFindPrefixArtifactConflicts_singleRun() throws Exception { | 
 |     ActionAnalysisMetadata action1 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/a")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo"))); | 
 |     ActionLookupValue alv1 = createMockActionLookupValueThatContains(action1); | 
 |  | 
 |     ActionAnalysisMetadata action2 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/b")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo/bar"))); | 
 |     ActionLookupValue alv2 = createMockActionLookupValueThatContains(action2); | 
 |  | 
 |     Sharder<ActionLookupValue> actionLookupValues = createALVSharderOf(alv1, alv2); | 
 |     MutableActionGraph actionGraph = new MapBasedActionGraph(new ActionKeyContext()); | 
 |     IncrementalArtifactConflictFinder conflictFinder = | 
 |         IncrementalArtifactConflictFinder.createWithActionGraph(actionGraph); | 
 |  | 
 |     ActionConflictsAndStats result = | 
 |         conflictFinder.findArtifactConflicts(actionLookupValues, /*strictConflictChecks=*/ true); | 
 |  | 
 |     assertThat(result.getConflicts()).hasSize(2); | 
 |     assertThat(result.getConflicts().get(action1)).isEqualTo(result.getConflicts().get(action2)); | 
 |     assertThrows( | 
 |         ArtifactPrefixConflictException.class, | 
 |         () -> result.getConflicts().get(action1).rethrowTyped()); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void testFindPrefixArtifactConflicts_noStrictChecks_expectNoConflict() throws Exception { | 
 |     ActionAnalysisMetadata action1 = | 
 |         new PrefixConflictTolerantDummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/a")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo"))); | 
 |     ActionLookupValue alv1 = createMockActionLookupValueThatContains(action1); | 
 |  | 
 |     ActionAnalysisMetadata action2 = | 
 |         new PrefixConflictTolerantDummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/b")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo/bar"))); | 
 |     ActionLookupValue alv2 = createMockActionLookupValueThatContains(action2); | 
 |  | 
 |     Sharder<ActionLookupValue> actionLookupValues = createALVSharderOf(alv1, alv2); | 
 |     MutableActionGraph actionGraph = new MapBasedActionGraph(new ActionKeyContext()); | 
 |     IncrementalArtifactConflictFinder conflictFinder = | 
 |         IncrementalArtifactConflictFinder.createWithActionGraph(actionGraph); | 
 |  | 
 |     ActionConflictsAndStats result = | 
 |         conflictFinder.findArtifactConflicts(actionLookupValues, /*strictConflictChecks=*/ false); | 
 |  | 
 |     assertThat(result.getConflicts()).isEmpty(); | 
 |   } | 
 |  | 
 |   @Test | 
 |   public void testFindPrefixArtifactConflicts_multithreaded() throws Exception { | 
 |     ActionAnalysisMetadata action1 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/a")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo"))); | 
 |     ActionLookupValue alv1 = createMockActionLookupValueThatContains(action1); | 
 |     Sharder<ActionLookupValue> actionLookupValues1 = createALVSharderOf(alv1); | 
 |  | 
 |     ActionAnalysisMetadata action2 = | 
 |         new DummyAction( | 
 |             createTestSourceArtifactWithPath(PathFragment.create("in/b")), | 
 |             createTestDerivedArtifactWithPath(PathFragment.create("out/foo/bar"))); | 
 |     ActionLookupValue alv2 = createMockActionLookupValueThatContains(action2); | 
 |     Sharder<ActionLookupValue> actionLookupValues2 = createALVSharderOf(alv2); | 
 |  | 
 |     MutableActionGraph actionGraph = new MapBasedActionGraph(new ActionKeyContext()); | 
 |     IncrementalArtifactConflictFinder conflictFinder = | 
 |         IncrementalArtifactConflictFinder.createWithActionGraph(actionGraph); | 
 |  | 
 |     ExecutorService executor = Executors.newFixedThreadPool(2); | 
 |     Future<ActionConflictsAndStats> future1 = | 
 |         executor.submit( | 
 |             () -> | 
 |                 conflictFinder.findArtifactConflicts( | 
 |                     actionLookupValues1, /*strictConflictChecks=*/ true)); | 
 |     Future<ActionConflictsAndStats> future2 = | 
 |         executor.submit( | 
 |             () -> | 
 |                 conflictFinder.findArtifactConflicts( | 
 |                     actionLookupValues2, /*strictConflictChecks=*/ true)); | 
 |     ActionConflictsAndStats withConflict = | 
 |         future1.get().getConflicts().isEmpty() ? future2.get() : future1.get(); | 
 |     executor.shutdownNow(); | 
 |  | 
 |     assertThat(withConflict.getConflicts()).hasSize(2); | 
 |     assertThat(withConflict.getConflicts().get(action1)) | 
 |         .isEqualTo(withConflict.getConflicts().get(action2)); | 
 |     assertThrows( | 
 |         ArtifactPrefixConflictException.class, | 
 |         () -> withConflict.getConflicts().get(action1).rethrowTyped()); | 
 |   } | 
 |  | 
 |   private Sharder<ActionLookupValue> createALVSharderOf(ActionLookupValue... actionLookupValues) { | 
 |     // These sizes are arbitrary. | 
 |     Sharder<ActionLookupValue> sharder = | 
 |         new Sharder<>(/*maxNumShards=*/ 8, /*expectedTotalSize*/ 10); | 
 |     for (ActionLookupValue actionLookupValue : actionLookupValues) { | 
 |       sharder.add(actionLookupValue); | 
 |     } | 
 |     return sharder; | 
 |   } | 
 |  | 
 |   private DerivedArtifact createTestDerivedArtifactWithPath(PathFragment path) { | 
 |     return DerivedArtifact.create( | 
 |         ArtifactRoot.asDerivedRoot(scratch.getFileSystem().getPath("/"), RootType.Output, "out"), | 
 |         path, | 
 |         mock(ActionLookupKey.class)); | 
 |   } | 
 |  | 
 |   private SourceArtifact createTestSourceArtifactWithPath(PathFragment path) { | 
 |     return new SourceArtifact( | 
 |         ArtifactRoot.asSourceRoot(Root.fromPath(scratch.getFileSystem().getPath("/"))), | 
 |         path, | 
 |         mock(ActionLookupKey.class)); | 
 |   } | 
 |  | 
 |   private ActionLookupValue createMockActionLookupValueThatContains(ActionAnalysisMetadata action) { | 
 |     ActionLookupValue actionLookupValue = mock(ActionLookupValue.class); | 
 |     when(actionLookupValue.getActions()).thenReturn(ImmutableList.of(action)); | 
 |     return actionLookupValue; | 
 |   } | 
 |  | 
 |   private static class PrefixConflictTolerantDummyAction extends DummyAction { | 
 |  | 
 |     public PrefixConflictTolerantDummyAction(Artifact input, Artifact output) { | 
 |       super(input, output); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean shouldReportPathPrefixConflict(ActionAnalysisMetadata action) { | 
 |       return false; | 
 |     } | 
 |   } | 
 | } |