blob: 9ca9b5dbd97b6634b01671181c45d2c4bb1d64c8 [file] [log] [blame]
// Copyright 2018 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.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.skyframe.TransitiveBaseTraversalFunction.TargetAndErrorIfAnyImpl;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.ValueOrException2;
import com.google.devtools.build.skyframe.ValueOrUntypedException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
/** Test for {@link TransitiveTraversalFunction}. */
@RunWith(JUnit4.class)
public class TransitiveTraversalFunctionTest extends BuildViewTestCase {
@Test
public void noRepeatedLabelVisitationForTransitiveTraversalFunction() throws Exception {
// Create a basic package with a target //foo:foo.
Label label = Label.parseAbsolute("//foo:foo", ImmutableMap.of());
Package pkg =
scratchPackage(
"workspace",
label.getPackageIdentifier(),
"sh_library(name = '" + label.getName() + "')");
TargetAndErrorIfAnyImpl targetAndErrorIfAny =
new TargetAndErrorIfAnyImpl(
/*packageLoadedSuccessfully=*/ true,
/*errorLoadingTarget=*/ null,
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
LoadTargetResults loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
// Create the GroupedList saying we had already requested two targets the last time we called
// #compute.
GroupedListHelper<SkyKey> helper = new GroupedListHelper<>();
SkyKey fakeDep1 = function.getKey(Label.parseAbsolute("//foo:bar", ImmutableMap.of()));
SkyKey fakeDep2 = function.getKey(Label.parseAbsolute("//foo:baz", ImmutableMap.of()));
helper.add(TargetMarkerValue.key(label));
helper.add(PackageValue.key(label.getPackageIdentifier()));
helper.startGroup();
// Note that these targets don't actually exist in the package we created initially. It doesn't
// matter for the purpose of this test, the original package was just to create some objects
// that we needed.
helper.add(fakeDep1);
helper.add(fakeDep2);
helper.endGroup();
GroupedList<SkyKey> groupedList = new GroupedList<>();
groupedList.append(helper);
AtomicBoolean wasOptimizationUsed = new AtomicBoolean(false);
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
when(mockEnv.getTemporaryDirectDeps()).thenReturn(groupedList);
when(mockEnv.getValuesOrThrow(
groupedList.get(2), NoSuchPackageException.class, NoSuchTargetException.class))
.thenAnswer(
(invocationOnMock) -> {
wasOptimizationUsed.set(true);
// It doesn't matter what this map is, we'll return false in the valuesMissing() call.
return ImmutableMap.of();
});
when(mockEnv.valuesMissing()).thenReturn(true);
// Run the compute function and check that we returned null.
assertThat(function.compute(function.getKey(label), mockEnv)).isNull();
// Verify that the mock was called with the arguments we expected.
assertThat(wasOptimizationUsed.get()).isTrue();
}
@Test
public void multipleErrorsForTransitiveTraversalFunction() throws Exception {
Label label = Label.parseAbsolute("//foo:foo", ImmutableMap.of());
Package pkg =
scratchPackage(
"workspace",
label.getPackageIdentifier(),
"sh_library(name = '" + label.getName() + "', deps = [':bar', ':baz'])");
TargetAndErrorIfAnyImpl targetAndErrorIfAny =
new TargetAndErrorIfAnyImpl(
/*packageLoadedSuccessfully=*/ true,
/*errorLoadingTarget=*/ null,
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
LoadTargetResults loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
SkyKey dep1 = function.getKey(Label.parseAbsolute("//foo:bar", ImmutableMap.of()));
SkyKey dep2 = function.getKey(Label.parseAbsolute("//foo:baz", ImmutableMap.of()));
ImmutableMap<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>
returnedDeps =
ImmutableMap.of(dep1, makeException("bad bar"), dep2, makeException("bad baz"));
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
// Try two evaluations, with the environment reversing the order of the map it returns.
when(mockEnv.getValuesOrThrow(
Mockito.any(),
Mockito.eq(NoSuchPackageException.class),
Mockito.eq(NoSuchTargetException.class)))
.thenReturn(returnedDeps);
when(mockEnv.valuesMissing()).thenReturn(false);
assertThat(
((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
.getErrorMessage())
.isEqualTo("bad bar");
ImmutableMap<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>
reversedDeps =
ImmutableMap.of(dep2, makeException("bad baz"), dep1, makeException("bad bar"));
when(mockEnv.getValuesOrThrow(
Mockito.any(),
Mockito.eq(NoSuchPackageException.class),
Mockito.eq(NoSuchTargetException.class)))
.thenReturn(reversedDeps);
assertThat(
((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
.getErrorMessage())
.isEqualTo("bad bar");
}
@Test
public void selfErrorWins() throws Exception {
Label label = Label.parseAbsolute("//foo:foo", ImmutableMap.of());
Package pkg =
scratchPackage(
"workspace",
label.getPackageIdentifier(),
"sh_library(name = '" + label.getName() + "', deps = [':bar', ':baz'])");
TargetAndErrorIfAnyImpl targetAndErrorIfAny =
new TargetAndErrorIfAnyImpl(
/*packageLoadedSuccessfully=*/ true,
/*errorLoadingTarget=*/ new NoSuchTargetException("self error is long and last"),
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
LoadTargetResults loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
SkyKey dep = function.getKey(Label.parseAbsolute("//foo:bar", ImmutableMap.of()));
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
when(mockEnv.getValuesOrThrow(
Mockito.any(),
Mockito.eq(NoSuchPackageException.class),
Mockito.eq(NoSuchTargetException.class)))
.thenReturn(ImmutableMap.of(dep, makeException("bad bar")));
when(mockEnv.valuesMissing()).thenReturn(false);
assertThat(
((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
.getErrorMessage())
.isEqualTo("self error is long and last");
}
private static ValueOrException2<NoSuchPackageException, NoSuchTargetException> makeException(
String errorMessage) {
ValueOrUntypedException exn =
ValueOrUntypedException.ofExn(new NoSuchTargetException(errorMessage));
return ValueOrException2.fromUntypedException(
exn, NoSuchPackageException.class, NoSuchTargetException.class);
}
private Package scratchPackage(String workspaceName, PackageIdentifier packageId, String... lines)
throws Exception {
Path buildFile = scratch.file("" + packageId.getSourceRoot() + "/BUILD", lines);
Package.Builder externalPkg =
Package.newExternalPackageBuilder(
Package.Builder.DefaultHelper.INSTANCE,
RootedPath.toRootedPath(root, buildFile.getRelative("WORKSPACE")),
"TESTING");
externalPkg.setWorkspaceName(workspaceName);
return pkgFactory.createPackageForTesting(
packageId,
externalPkg.build(),
RootedPath.toRootedPath(root, buildFile),
packageIdentifier -> buildFile,
reporter,
StarlarkSemantics.DEFAULT_SEMANTICS);
}
}