blob: 10baaa3f65da76a49ce63e6cf55be3a3ce22b8ba [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.analysis;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Correspondence;
import com.google.devtools.build.lib.analysis.test.AnalysisFailure;
import com.google.devtools.build.lib.analysis.test.AnalysisFailureInfo;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.analysis.util.MockRule;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.Depset;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import javax.annotation.Nullable;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests verifying analysis failure propagation via {@link AnalysisFailureInfo} when {@code
* --allow_analysis_failures=true}.
*/
@RunWith(TestParameterInjector.class)
public final class AnalysisFailureInfoTest extends BuildViewTestCase {
@Before
public void setUp() throws Exception {
useConfiguration("--allow_analysis_failures=true");
}
@Test
public void analysisFailureInfoStarlarkApi() throws Exception {
Label label = Label.create("test", "test");
AnalysisFailure failure = AnalysisFailure.create(label, "ErrorMessage");
assertThat(getattr(failure, "label")).isSameInstanceAs(label);
assertThat(getattr(failure, "message")).isEqualTo("ErrorMessage");
AnalysisFailureInfo info = AnalysisFailureInfo.forAnalysisFailures(ImmutableList.of(failure));
// info.causes.to_list()[0] == failure
NestedSet<AnalysisFailure> causes =
Depset.cast(getattr(info, "causes"), AnalysisFailure.class, "causes");
assertThat(causes.toList().get(0)).isSameInstanceAs(failure);
}
private static Object getattr(Object x, String name) throws Exception {
return Starlark.getattr(/*mu=*/ null, StarlarkSemantics.DEFAULT, x, name, null);
}
/** Regression test for b/154007057 (rule name) and b/186685477 (output file). */
@Test
public void nativeRuleExpanderFailure(
@TestParameter({"//test:bad_variable", "//test:bad_variable.out"}) String targetToRequest)
throws Exception {
scratch.file(
"test/BUILD",
"genrule(",
" name = 'bad_variable',",
" outs = ['bad_variable.out'],",
" cmd = 'cp $< $@', # Error to use $< with no srcs",
")");
ConfiguredTarget target = getConfiguredTarget(targetToRequest);
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
assertThat(failure.getMessage()).contains("variable '$<' : no input file");
assertThat(failure.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:bad_variable"));
}
/** Regression test for b/154007057. */
@Test
public void nativeRuleConfiguredTargetFactoryCreateReturningNull() throws Exception {
scratch.file(
"test/BUILD",
"native_rule_with_failing_configured_target_factory(",
" name = 'bad_factory',",
")");
ConfiguredTarget target = getConfiguredTarget("//test:bad_factory");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
assertThat(failure.getMessage()).contains("FailingRuleConfiguredTargetFactory.create() fails");
assertThat(failure.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:bad_factory"));
}
/** Dummy factory whose {@code create()} method always returns {@code null}. */
public static final class FailingRuleConfiguredTargetFactory
implements RuleConfiguredTargetFactory {
@Override
@Nullable
public ConfiguredTarget create(RuleContext ruleContext) {
ruleContext.ruleError("FailingRuleConfiguredTargetFactory.create() fails");
return null;
}
}
@Test
public void analysisTestNotReturningAnalysisTestResultInfo_cannotPropagate() throws Exception {
scratch.file(
"test/BUILD", //
"providerless_analysis_test(name = 'providerless')");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:providerless");
assertContainsEvent(
"Error while collecting analysis-phase failure information for '//test:providerless': rules"
+ " with analysis_test=true must return an instance of AnalysisTestResultInfo");
}
/** Regression test for b/233890545 */
@Test
public void analysisTestExpectingFailureDependedOnByAnalysisTest_cannotPropagate()
throws Exception {
useConfiguration("--allow_analysis_failures=false");
scratch.file(
"test/extension.bzl",
"def bad_rule_impl(ctx):",
" fail('Bad rule fails')",
"",
"bad_rule = rule(",
" implementation = bad_rule_impl,",
" attrs = {'dep': attr.label()}",
")",
"",
"def analysis_test_impl(ctx):",
" return [AnalysisTestResultInfo(success = False, message = 'Expect failure')]",
"",
"_transition = analysis_test_transition(",
" settings = {'//command_line_option:allow_analysis_failures': 'True'}",
")",
"",
"analysis_test = rule(",
" implementation = analysis_test_impl,",
" analysis_test = True,",
" attrs = {'dep': attr.label(cfg = _transition)}",
")");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'bad_rule', 'analysis_test')",
"",
"analysis_test(name = 'outer', dep = ':inner')",
"analysis_test(name = 'inner', dep = ':tested_by_inner')",
"bad_rule(name = 'tested_by_inner')");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:outer");
assertContainsEvent(
"Error while collecting analysis-phase failure information for '//test:inner':"
+ " analysis_test rule '//test:inner' cannot be transitively depended on by another"
+ " analysis test rule");
}
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
ConfiguredRuleClassProvider.Builder builder =
new ConfiguredRuleClassProvider.Builder()
.addRuleDefinition(
((MockRule)
() ->
MockRule.factory(FailingRuleConfiguredTargetFactory.class)
.define("native_rule_with_failing_configured_target_factory")))
.addRuleDefinition(
(MockRule)
() ->
MockRule.ancestor(
BaseRuleClasses.TestBaseRule.class,
BaseRuleClasses.NativeBuildRule.class)
.type(RuleClassType.TEST)
.define(
"providerless_analysis_test",
(ruleClassBuilder, env) -> ruleClassBuilder.setIsAnalysisTest()));
TestRuleClassProvider.addStandardRules(builder);
return builder.build();
}
private static final Correspondence<AnalysisFailure, AnalysisFailure>
analysisFailureCorrespondence =
Correspondence.from(
(actual, expected) ->
actual.getLabel().equals(expected.getLabel())
&& actual.getMessage().contains(expected.getMessage()),
"is equivalent to");
@Test
public void starlarkRuleFailure() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_rule_impl(ctx):",
" fail('This Is My Failure Message')",
"",
"custom_rule = rule(implementation = custom_rule_impl)");
scratch.file(
"test/BUILD", "load('//test:extension.bzl', 'custom_rule')", "", "custom_rule(name = 'r')");
ConfiguredTarget target = getConfiguredTarget("//test:r");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
assertThat(failure.getMessage()).contains("This Is My Failure Message");
assertThat(failure.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:r"));
}
@Test
public void starlarkRuleFailure_forTest() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_rule_impl(ctx):",
" fail('This Is My Failure Message')",
"",
"custom_test = rule(implementation = custom_rule_impl,",
" test = True)");
scratch.file(
"test/BUILD", "load('//test:extension.bzl', 'custom_test')", "", "custom_test(name = 'r')");
ConfiguredTarget target = getConfiguredTarget("//test:r");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
assertThat(failure.getMessage()).contains("This Is My Failure Message");
assertThat(failure.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:r"));
}
@Test
public void starlarkRuleFailure_withOutput() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_rule_impl(ctx):",
" fail('This Is My Failure Message')",
"",
"custom_rule = rule(implementation = custom_rule_impl,",
" outputs = {'my_output': '%{name}.txt'})");
scratch.file(
"test/BUILD", "load('//test:extension.bzl', 'custom_rule')", "", "custom_rule(name = 'r')");
ConfiguredTarget target = getConfiguredTarget("//test:r");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure failure = info.getCauses().getSet(AnalysisFailure.class).toList().get(0);
assertThat(failure.getMessage()).contains("This Is My Failure Message");
assertThat(failure.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:r"));
}
@Test
public void transitiveStarlarkRuleFailure() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_rule_impl(ctx):",
" fail('This Is My Failure Message')",
"",
"custom_rule = rule(implementation = custom_rule_impl)",
"",
"def depending_rule_impl(ctx):",
" return []",
"",
"depending_rule = rule(implementation = depending_rule_impl,",
" attrs = {'deps' : attr.label_list()})");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'custom_rule', 'depending_rule')",
"",
"custom_rule(name = 'one')",
"custom_rule(name = 'two')",
"depending_rule(name = 'failures_are_direct_deps',",
" deps = [':one', ':two'])",
"depending_rule(name = 'failures_are_indirect_deps',",
" deps = [':failures_are_direct_deps'])");
ConfiguredTarget target = getConfiguredTarget("//test:failures_are_indirect_deps");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure expectedOne =
AnalysisFailure.create(
Label.parseCanonicalUnchecked("//test:one"), "This Is My Failure Message");
AnalysisFailure expectedTwo =
AnalysisFailure.create(
Label.parseCanonicalUnchecked("//test:two"), "This Is My Failure Message");
assertThat(info.getCausesNestedSet().toList())
.comparingElementsUsing(analysisFailureCorrespondence)
.containsExactly(expectedOne, expectedTwo);
}
@Test
public void starlarkAspectFailure() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_aspect_impl(target, ctx):",
" fail('This Is My Aspect Failure Message')",
"",
"custom_aspect = aspect(implementation = custom_aspect_impl, attr_aspects = ['deps'])",
"",
"def custom_rule_impl(ctx):",
" return []",
"",
"custom_rule = rule(implementation = custom_rule_impl,",
" attrs = {'deps' : attr.label_list(aspects = [custom_aspect])})");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'custom_rule')",
"",
"custom_rule(name = 'one')",
"custom_rule(name = 'two', deps = [':one'])");
ConfiguredTarget target = getConfiguredTarget("//test:two");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure expectedOne =
AnalysisFailure.create(
Label.parseCanonicalUnchecked("//test:one"), "This Is My Aspect Failure Message");
assertThat(info.getCausesNestedSet().toList())
.comparingElementsUsing(analysisFailureCorrespondence)
.containsExactly(expectedOne);
}
@Test
public void transitiveStarlarkAspectFailure() throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_aspect_impl(target, ctx):",
" if hasattr(ctx.rule.attr, 'kaboom') and ctx.rule.attr.kaboom:",
" fail('This Is My Aspect Failure Message')",
" return []",
"",
"custom_aspect = aspect(implementation = custom_aspect_impl, attr_aspects = ['deps'])",
"",
"def custom_rule_impl(ctx):",
" return []",
"",
"custom_rule = rule(implementation = custom_rule_impl,",
" attrs = {'deps' : attr.label_list(aspects = [custom_aspect]),",
" 'kaboom' : attr.bool()})");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'custom_rule')",
"",
"custom_rule(name = 'one', kaboom = True)",
"custom_rule(name = 'two', deps = [':one'])",
"custom_rule(name = 'three', deps = [':two'])");
ConfiguredTarget target = getConfiguredTarget("//test:three");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure expectedOne =
AnalysisFailure.create(
Label.parseCanonicalUnchecked("//test:one"), "This Is My Aspect Failure Message");
assertThat(info.getCausesNestedSet().toList())
.comparingElementsUsing(analysisFailureCorrespondence)
.containsExactly(expectedOne);
}
@Test
public void starlarkAspectAndRuleFailure_analysisFailureInfoPropagatesOnlyFromRuleFailure()
throws Exception {
scratch.file(
"test/extension.bzl",
"def custom_aspect_impl(target, ctx):",
" fail('This Is My Aspect Failure Message')",
"",
"custom_aspect = aspect(implementation = custom_aspect_impl, attr_aspects = ['deps'])",
"",
"def custom_rule_impl(ctx):",
" fail('This Is My Rule Failure Message')",
"",
"custom_rule = rule(implementation = custom_rule_impl,",
" attrs = {'deps' : attr.label_list(aspects = [custom_aspect])})");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'custom_rule')",
"",
"custom_rule(name = 'one')",
"custom_rule(name = 'two', deps = [':one'])");
ConfiguredTarget target = getConfiguredTarget("//test:two");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure expectedRuleFailure =
AnalysisFailure.create(
Label.parseCanonicalUnchecked("//test:one"), "This Is My Rule Failure Message");
assertThat(info.getCausesNestedSet().toList())
.comparingElementsUsing(analysisFailureCorrespondence)
.containsExactly(expectedRuleFailure);
}
@Test
public void starlarkAspectWithAdvertisedProvidersFailure_analysisFailurePropagates()
throws Exception {
scratch.file(
"test/extension.bzl",
"MyInfo = provider()",
"",
"def custom_aspect_impl(target, ctx):",
" fail('Aspect Failure')",
"",
"custom_aspect = aspect(implementation = custom_aspect_impl, provides = [MyInfo])",
"",
"def custom_rule_impl(ctx):",
" pass",
"",
"custom_rule = rule(implementation = custom_rule_impl,",
" attrs = {'deps' : attr.label_list(aspects = [custom_aspect])})");
scratch.file(
"test/BUILD",
"load('//test:extension.bzl', 'custom_rule')",
"",
"custom_rule(name = 'one')",
"custom_rule(name = 'two', deps = [':one'])");
ConfiguredTarget target = getConfiguredTarget("//test:two");
AnalysisFailureInfo info =
(AnalysisFailureInfo) target.get(AnalysisFailureInfo.STARLARK_CONSTRUCTOR.getKey());
AnalysisFailure expectedRuleFailure =
AnalysisFailure.create(Label.parseCanonicalUnchecked("//test:one"), "Aspect Failure");
assertThat(info.getCausesNestedSet().toList())
.comparingElementsUsing(analysisFailureCorrespondence)
.containsExactly(expectedRuleFailure);
}
}