blob: 529db8917fd150010ecbddd60a07dd9d882d5458 [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.ImmutableList;
import com.google.common.collect.ImmutableSet;
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.TargetLoadingUtil.TargetAndErrorIfAny;
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.GroupedDeps;
import com.google.devtools.build.skyframe.SimpleSkyframeLookupResult;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
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.parseCanonical("//foo:foo");
scratch.file("foo/BUILD", "sh_library(name = '" + label.getName() + "')");
Package pkg = loadPackage(label.getPackageIdentifier());
TargetAndErrorIfAny targetAndErrorIfAny =
new TargetAndErrorIfAny(
/* packageLoadedSuccessfully= */ true,
/* errorLoadingTarget= */ null,
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
TargetAndErrorIfAny loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
// Create the GroupedDeps saying we had already requested two targets the last time we called
// #compute.
GroupedDeps groupedDeps = new GroupedDeps();
groupedDeps.appendSingleton(label.getPackageIdentifier());
// 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.
SkyKey fakeDep1 = function.getKey(Label.parseCanonical("//foo:bar"));
SkyKey fakeDep2 = function.getKey(Label.parseCanonical("//foo:baz"));
groupedDeps.appendGroup(ImmutableList.of(fakeDep1, fakeDep2));
AtomicBoolean wasOptimizationUsed = new AtomicBoolean(false);
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
when(mockEnv.getTemporaryDirectDeps()).thenReturn(groupedDeps);
when(mockEnv.getValuesAndExceptions(groupedDeps.getDepGroup(1)))
.thenAnswer(
(invocationOnMock) -> {
wasOptimizationUsed.set(true);
// It doesn't matter what this SkyframeLookupResult is, we'll return true in the
// valuesMissing() call.
return new SimpleSkyframeLookupResult(
/* valuesMissingCallback= */ () -> {},
k -> {
throw new IllegalStateException("Shouldn't have been called: " + k);
});
});
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.parseCanonical("//foo:foo");
scratch.file(
"foo/BUILD", "sh_library(name = '" + label.getName() + "', deps = [':bar', ':baz'])");
Package pkg = loadPackage(label.getPackageIdentifier());
TargetAndErrorIfAny targetAndErrorIfAny =
new TargetAndErrorIfAny(
/* packageLoadedSuccessfully= */ true,
/* errorLoadingTarget= */ null,
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
TargetAndErrorIfAny loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
SkyKey dep1 = function.getKey(Label.parseCanonical("//foo:bar"));
SkyKey dep2 = function.getKey(Label.parseCanonical("//foo:baz"));
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
NoSuchTargetException exp1 = new NoSuchTargetException("bad bar");
NoSuchTargetException exp2 = new NoSuchTargetException("bad baz");
SkyframeLookupResult returnedDeps =
new SimpleSkyframeLookupResult(
() -> {},
key ->
key.equals(dep1)
? ValueOrUntypedException.ofExn(exp1)
: key.equals(dep2) ? ValueOrUntypedException.ofExn(exp2) : null);
when(mockEnv.getValuesAndExceptions(ImmutableSet.of(dep1, dep2))).thenReturn(returnedDeps);
when(mockEnv.valuesMissing()).thenReturn(false);
assertThat(
((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
.getErrorMessage())
.isEqualTo("bad bar");
}
@Test
public void selfErrorWins() throws Exception {
Label label = Label.parseCanonical("//foo:foo");
scratch.file("foo/BUILD", "sh_library(name = '" + label.getName() + "', deps = [':bar'])");
Package pkg = loadPackage(label.getPackageIdentifier());
TargetAndErrorIfAny targetAndErrorIfAny =
new TargetAndErrorIfAny(
/* packageLoadedSuccessfully= */ true,
/* errorLoadingTarget= */ new NoSuchTargetException("self error is long and last"),
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
TargetAndErrorIfAny loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
SkyKey dep = function.getKey(Label.parseCanonical("//foo:bar"));
NoSuchTargetException exp = new NoSuchTargetException("bad bar");
SkyframeLookupResult returnedDep =
new SimpleSkyframeLookupResult(
() -> {}, key -> key.equals(dep) ? ValueOrUntypedException.ofExn(exp) : null);
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
when(mockEnv.getValuesAndExceptions(ImmutableSet.of(dep))).thenReturn(returnedDep);
when(mockEnv.valuesMissing()).thenReturn(false);
TransitiveTraversalValue transitiveTraversalValue =
(TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv);
assertThat(transitiveTraversalValue.getErrorMessage()).isEqualTo("self error is long and last");
}
@Test
public void getStrictLabelAspectKeys() throws Exception {
Label label = Label.parseCanonical("//test:foo");
scratch.file(
"test/aspect.bzl",
"def _aspect_impl(target, ctx):",
" return struct()",
"def _rule_impl(ctx):",
" return struct()",
"",
"MyAspect = aspect(",
" implementation=_aspect_impl,",
" attr_aspects=['deps'],",
" attrs = { '_extra_deps' : attr.label(default = Label('//foo:bar'))},",
")",
"my_rule = rule(",
" implementation=_rule_impl,",
" attrs = { 'attr' : ",
" attr.label_list(mandatory=True, aspects = [MyAspect]) ",
" },",
")");
scratch.file(
"test/BUILD",
"load('//test:aspect.bzl', 'my_rule')",
"my_rule(name = 'foo',attr = [':bad'])");
Package pkg = loadPackage(label.getPackageIdentifier());
TargetAndErrorIfAny targetAndErrorIfAny =
new TargetAndErrorIfAny(
/* packageLoadedSuccessfully= */ true,
/* errorLoadingTarget= */ null,
pkg.getTarget(label.getName()));
TransitiveTraversalFunction function =
new TransitiveTraversalFunction() {
@Override
TargetAndErrorIfAny loadTarget(Environment env, Label label) {
return targetAndErrorIfAny;
}
};
SkyKey badDep = function.getKey(Label.parseCanonical("//test:bad"));
NoSuchTargetException exp = new NoSuchTargetException("bad test");
AtomicBoolean valuesMissing = new AtomicBoolean(false);
SkyframeLookupResult returnedDep =
new SimpleSkyframeLookupResult(
() -> valuesMissing.set(true),
key -> key.equals(badDep) ? ValueOrUntypedException.ofExn(exp) : null);
SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
when(mockEnv.getValuesAndExceptions(ImmutableSet.of(badDep))).thenReturn(returnedDep);
TransitiveTraversalValue transitiveTraversalValue =
(TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv);
assertThat(transitiveTraversalValue.getErrorMessage()).isEqualTo("bad test");
assertThat(valuesMissing.get()).isFalse();
}
/* Invokes the loading phase, using Skyframe. */
private Package loadPackage(PackageIdentifier pkgid)
throws InterruptedException, NoSuchPackageException {
EvaluationResult<PackageValue> result =
SkyframeExecutorTestUtils.evaluate(
getSkyframeExecutor(), pkgid, /* keepGoing= */ false, reporter);
if (result.hasError()) {
throw (NoSuchPackageException) result.getError(pkgid).getException();
}
return result.get(pkgid).getPackage();
}
}