| // 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); |
| } |
| } |