// Copyright 2015 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 static com.google.common.truth.Truth.assertWithMessage; import static com.google.devtools.build.lib.packages.Attribute.attr; import static com.google.devtools.build.lib.testutil.MoreAsserts.assertEventCountAtLeast; import static org.junit.Assert.fail; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Actions; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.FailAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.transitions.NoTransition; import com.google.devtools.build.lib.analysis.configuredtargets.InputFileConfiguredTarget; import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget; import com.google.devtools.build.lib.analysis.util.BuildViewTestBase; import com.google.devtools.build.lib.analysis.util.ExpectedTrimmedConfigurationErrors; import com.google.devtools.build.lib.analysis.util.MockRule; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.events.OutputFilter.RegexOutputFilter; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.pkgcache.LoadingFailureEvent; import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; import com.google.devtools.build.lib.testutil.Suite; import com.google.devtools.build.lib.testutil.TestConstants; import com.google.devtools.build.lib.testutil.TestRuleClassProvider; import com.google.devtools.build.lib.testutil.TestSpec; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.skyframe.NotifyingHelper.EventType; import com.google.devtools.build.skyframe.NotifyingHelper.Listener; import com.google.devtools.build.skyframe.NotifyingHelper.Order; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsParsingException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link BuildView}. */ @TestSpec(size = Suite.SMALL_TESTS) @RunWith(JUnit4.class) public class BuildViewTest extends BuildViewTestBase { private static final Function> ANALYSIS_EVENT_TO_STRING_PAIR = new Function>() { @Override public Pair apply(AnalysisFailureEvent event) { return Pair.of( event.getFailedTarget().getLabel().toString(), event.getLegacyFailureReason().toString()); } }; @Test public void testRuleConfiguredTarget() throws Exception { scratch.file("pkg/BUILD", "genrule(name='foo', ", " cmd = '',", " srcs=['a.src'],", " outs=['a.out'])"); update("//pkg:foo"); Rule ruleTarget = (Rule) getTarget("//pkg:foo"); assertThat(ruleTarget.getRuleClass()).isEqualTo("genrule"); ConfiguredTargetAndData ruleCTAT = getConfiguredTargetAndTarget("//pkg:foo"); assertThat(ruleCTAT.getTarget()).isSameAs(ruleTarget); } @Test public void testFilterByTargets() throws Exception { scratch.file("tests/BUILD", "sh_test(name = 'small_test_1',", " srcs = ['small_test_1.sh'],", " data = [':xUnit'],", " size = 'small',", " tags = ['tag1'])", "", "sh_test(name = 'small_test_2',", " srcs = ['small_test_2.sh'],", " size = 'small',", " tags = ['tag2'])", "", "", "test_suite( name = 'smallTests', tags=['small'])"); //scratch.file("tests/small_test_1.py"); update("//tests:smallTests"); ConfiguredTargetAndData test1 = getConfiguredTargetAndTarget("//tests:small_test_1"); ConfiguredTargetAndData test2 = getConfiguredTargetAndTarget("//tests:small_test_2"); ConfiguredTargetAndData suite = getConfiguredTargetAndTarget("//tests:smallTests"); ConfiguredTarget test1CT = test1.getConfiguredTarget(); ConfiguredTarget test2CT = test2.getConfiguredTarget(); ConfiguredTarget suiteCT = suite.getConfiguredTarget(); assertNoEvents(); // start from a clean slate Collection targets = new LinkedHashSet<>(ImmutableList.of(test1CT, test2CT, suiteCT)); targets = Lists.newArrayList( BuildView.filterTestsByTargets( targets, Sets.newHashSet(test1.getTarget().getLabel(), suite.getTarget().getLabel()))); assertThat(targets).containsExactlyElementsIn(Sets.newHashSet(test1CT, suiteCT)); } @Test public void testSourceArtifact() throws Exception { setupDummyRule(); update("//pkg:a.src"); InputFileConfiguredTarget inputCT = getInputFileConfiguredTarget("//pkg:a.src"); Artifact inputArtifact = inputCT.getArtifact(); assertThat(getGeneratingAction(inputArtifact)).isNull(); assertThat(inputArtifact.getExecPathString()).isEqualTo("pkg/a.src"); } @Test public void testGeneratedArtifact() throws Exception { setupDummyRule(); update("//pkg:a.out"); ConfiguredTargetAndData ctad = getConfiguredTargetAndData("//pkg:a.out"); OutputFileConfiguredTarget output = (OutputFileConfiguredTarget) ctad.getConfiguredTarget(); Artifact outputArtifact = output.getArtifact(); assertThat(outputArtifact.getRoot()) .isEqualTo( ctad.getConfiguration() .getBinDirectory(output.getLabel().getPackageIdentifier().getRepository())); assertThat(outputArtifact.getExecPath()) .isEqualTo(ctad.getConfiguration().getBinFragment().getRelative("pkg/a.out")); assertThat(outputArtifact.getRootRelativePath()).isEqualTo(PathFragment.create("pkg/a.out")); Action action = getGeneratingAction(outputArtifact); assertThat(action.getClass()).isSameAs(FailAction.class); } @Test public void testSyntaxErrorInDepPackage() throws Exception { // Check that a loading error in a dependency is properly reported. scratch.file("a/BUILD", "genrule(name='x',", " srcs = ['file.txt'],", " outs = ['foo'],", " cmd = 'echo')", "@"); // syntax error scratch.file("b/BUILD", "genrule(name= 'cc',", " tools = ['//a:x'],", " outs = ['bar'],", " cmd = 'echo')"); reporter.removeHandler(failFastHandler); EventBus eventBus = new EventBus(); AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//b:cc"); assertContainsEvent("invalid character: '@'"); assertThat(result.hasError()).isTrue(); } @Test public void testReportsAnalysisRootCauses() throws Exception { scratch.file("private/BUILD", "genrule(", " name='private',", " outs=['private.out'],", " cmd='',", " visibility=['//visibility:private'])"); scratch.file("foo/BUILD", "genrule(", " name='foo',", " tools=[':bar'],", " outs=['foo.out'],", " cmd='')", "genrule(", " name='bar',", " tools=['//private'],", " outs=['bar.out'],", " cmd='')"); reporter.removeHandler(failFastHandler); EventBus eventBus = new EventBus(); AnalysisFailureRecorder recorder = new AnalysisFailureRecorder(); eventBus.register(recorder); AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//foo"); assertThat(result.hasError()).isTrue(); assertThat(recorder.events).hasSize(1); AnalysisFailureEvent event = recorder.events.get(0); assertThat(event.getLegacyFailureReason().toString()).isEqualTo("//foo:bar"); assertThat(event.getFailedTarget().getLabel().toString()).isEqualTo("//foo:foo"); } @Test public void testReportsLoadingRootCauses() throws Exception { // This test checks that two simultaneous errors are both reported: // - missing outs attribute, // - package referenced in tools does not exist scratch.file("pkg/BUILD", "genrule(name='foo',", " tools=['//nopackage:missing'],", " cmd='')"); reporter.removeHandler(failFastHandler); EventBus eventBus = new EventBus(); LoadingFailureRecorder recorder = new LoadingFailureRecorder(); eventBus.register(recorder); // Note: no need to run analysis for a loading failure. AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//pkg:foo"); assertThat(result.hasError()).isTrue(); assertThat(recorder.events) .contains( new LoadingFailureEvent( Label.parseAbsolute("//pkg:foo", ImmutableMap.of()), Label.parseAbsolute("//nopackage:missing", ImmutableMap.of()))); assertContainsEvent("missing value for mandatory attribute 'outs'"); assertContainsEvent("no such package 'nopackage'"); // Skyframe correctly reports the other root cause as the genrule itself (since it is // missing attributes). assertThat(recorder.events).hasSize(2); assertThat(recorder.events) .contains( new LoadingFailureEvent( Label.parseAbsolute("//pkg:foo", ImmutableMap.of()), Label.parseAbsolute("//pkg:foo", ImmutableMap.of()))); } @Test public void testConvolutedLoadRootCauseAnalysis() throws Exception { // You need license declarations in third_party. We use this constraint to // create targets that are loadable, but are in error. scratch.file("third_party/first/BUILD", "sh_library(name='first', deps=['//third_party/second'], licenses=['notice'])"); scratch.file("third_party/second/BUILD", "sh_library(name='second', deps=['//third_party/third'], licenses=['notice'])"); scratch.file("third_party/third/BUILD", "sh_library(name='third', deps=['//third_party/fourth'], licenses=['notice'])"); scratch.file("third_party/fourth/BUILD", "sh_library(name='fourth', deps=['//third_party/fifth'])"); scratch.file("third_party/fifth/BUILD", "sh_library(name='fifth', licenses=['notice'])"); reporter.removeHandler(failFastHandler); EventBus eventBus = new EventBus(); LoadingFailureRecorder recorder = new LoadingFailureRecorder(); eventBus.register(recorder); // Note: no need to run analysis for a loading failure. AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//third_party/first", "//third_party/third"); assertThat(result.hasError()).isTrue(); assertThat(recorder.events).hasSize(2); assertWithMessage(recorder.events.toString()) .that( recorder.events.contains( new LoadingFailureEvent( Label.parseAbsolute("//third_party/first", ImmutableMap.of()), Label.parseAbsolute("//third_party/fourth", ImmutableMap.of())))) .isTrue(); assertThat(recorder.events) .contains( new LoadingFailureEvent( Label.parseAbsolute("//third_party/third", ImmutableMap.of()), Label.parseAbsolute("//third_party/fourth", ImmutableMap.of()))); } @Test public void testMultipleRootCauseReporting() throws Exception { scratch.file("gp/BUILD", "sh_library(name = 'gp', deps = ['//p:p'])"); scratch.file("p/BUILD", "sh_library(name = 'p', deps = ['//c1:not', '//c2:not'])"); scratch.file("c1/BUILD"); scratch.file("c2/BUILD"); reporter.removeHandler(failFastHandler); EventBus eventBus = new EventBus(); LoadingFailureRecorder recorder = new LoadingFailureRecorder(); eventBus.register(recorder); AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//gp"); assertThat(result.hasError()).isTrue(); assertThat(recorder.events).hasSize(2); assertThat(recorder.events) .contains( new LoadingFailureEvent( Label.parseAbsolute("//gp", ImmutableMap.of()), Label.parseAbsolute("//c1:not", ImmutableMap.of()))); assertThat(recorder.events) .contains( new LoadingFailureEvent( Label.parseAbsolute("//gp", ImmutableMap.of()), Label.parseAbsolute("//c2:not", ImmutableMap.of()))); } /** * Regression test for: "Package group includes are broken" */ @Test public void testTopLevelPackageGroup() throws Exception { scratch.file("tropical/BUILD", "package_group(name='guava', includes=[':mango'])", "package_group(name='mango')"); // If the analysis phase results in an error, this will throw an exception update("//tropical:guava"); // Check if the included package group also got analyzed assertThat(getConfiguredTarget("//tropical:mango", null)).isNotNull(); } @Test public void testTopLevelInputFile() throws Exception { scratch.file("tropical/BUILD", "exports_files(['file.txt'])"); update("//tropical:file.txt"); assertThat(getConfiguredTarget("//tropical:file.txt", null)).isNotNull(); } @Test public void testGetDirectPrerequisites() throws Exception { scratch.file( "package/BUILD", "filegroup(name='top', srcs=[':inner', 'file'])", "sh_binary(name='inner', srcs=['script.sh'])"); update("//package:top"); ConfiguredTarget top = getConfiguredTarget("//package:top", getTargetConfiguration()); Iterable targets = getView().getDirectPrerequisitesForTesting( reporter, top, getBuildConfigurationCollection()); Iterable