| // 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", "analysis_test", "bad_rule") | |
| 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); | |
| } | |
| } |