blob: 4a4ea4c7bbb5d5d48acc286eb27ef95b2c3eca8e [file] [log] [blame]
// 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;
}
}
}