blob: 3c33512b48b1afadb86546594ce0012ac783a1a7 [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.analysis;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.ConfigurationId;
import com.google.devtools.build.lib.causes.AnalysisFailedCause;
import com.google.devtools.build.lib.causes.Cause;
import com.google.devtools.build.lib.causes.LoadingFailedCause;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.testutil.Suite;
import com.google.devtools.build.lib.testutil.TestSpec;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Analysis failure reporting tests.
*/
@TestSpec(size = Suite.SMALL_TESTS)
@RunWith(JUnit4.class)
public class AnalysisFailureReportingTest extends AnalysisTestCase {
private final AnalysisFailureEventCollector collector = new AnalysisFailureEventCollector();
// TODO(ulfjack): Don't check for exact error message wording; instead, add machine-readable
// details to the events, and check for those. Also check if we can remove duplicate test coverage
// for these errors, i.e., consolidate the failure reporting tests in this class.
@Before
public void setup() {
// We only test failure cases in this class.
reporter.removeHandler(failFastHandler);
eventBus.register(collector);
}
private static ConfigurationId toId(BuildConfiguration config) {
return config == null ? null : config.getEventId().asStreamProto().getConfiguration();
}
@Test
public void testMissingRequiredAttribute() throws Exception {
scratch.file(
"foo/BUILD",
"genrule(name = 'foo',", // missing "out" attribute
" cmd = '')");
AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//foo");
assertThat(result.hasError()).isTrue();
Label topLevel = Label.parseAbsoluteUnchecked("//foo");
assertThat(collector.events.keySet()).containsExactly(topLevel);
assertThat(collector.events.get(topLevel))
.containsExactly(
new LoadingFailedCause(
topLevel, "Target '//foo:foo' contains an error and its package is in error"));
}
@Test
public void testMissingDependency() throws Exception {
scratch.file("foo/BUILD",
"genrule(name = 'foo',",
" tools = ['//bar'],",
" cmd = 'command',",
" outs = ['foo.txt'])");
AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//foo");
assertThat(result.hasError()).isTrue();
Label topLevel = Label.parseAbsoluteUnchecked("//foo");
Label causeLabel = Label.parseAbsoluteUnchecked("//bar");
assertThat(collector.events.keySet()).containsExactly(topLevel);
assertThat(collector.events.get(topLevel))
.containsExactly(
new LoadingFailedCause(
causeLabel,
"no such package 'bar': BUILD file not found in any of the following "
+ "directories. Add a BUILD file to a directory to mark it as a package.\n"
+ " - /workspace/bar"));
}
/**
* This error gets reported twice - once when we try to analyze the //cycles1 target, and the
* other time when we analyze the //c target (which depends on //cycles1). This test checks that
* both use the same error message.
*/
@Test
public void testSymlinkCycleReportedExactlyOnce() throws Exception {
scratch.file("gp/BUILD",
"sh_library(name = 'gp', deps = ['//p'])");
scratch.file("p/BUILD",
"sh_library(name = 'p', deps = ['//c'])");
scratch.file("c/BUILD",
"sh_library(name = 'c', deps = ['//cycles1'])");
Path cycles1BuildFilePath = scratch.file("cycles1/BUILD",
"sh_library(name = 'cycles1', srcs = glob(['*.sh']))");
cycles1BuildFilePath
.getParentDirectory()
.getRelative("cycles1.sh")
.createSymbolicLink(PathFragment.create("cycles1.sh"));
AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//gp");
assertThat(result.hasError()).isTrue();
Label topLevel = Label.parseAbsoluteUnchecked("//gp");
assertThat(collector.events.get(topLevel))
.containsExactly(
new LoadingFailedCause(
Label.parseAbsolute("//cycles1", ImmutableMap.of()),
"no such package 'cycles1': Symlink issue while evaluating globs: Symlink cycle: "
+ "/workspace/cycles1/cycles1.sh"));
}
@Test
public void testVisibilityError() throws Exception {
scratch.file("foo/BUILD",
"sh_library(name = 'foo', deps = ['//bar'])");
scratch.file("bar/BUILD",
"sh_library(name = 'bar', visibility = ['//visibility:private'])");
AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//foo");
assertThat(result.hasError()).isTrue();
Label topLevel = Label.parseAbsoluteUnchecked("//foo");
assertThat(collector.events.get(topLevel))
.containsExactly(
new AnalysisFailedCause(
Label.parseAbsolute("//foo", ImmutableMap.of()),
toId(
Iterables.getOnlyElement(result.getTopLevelTargetsWithConfigs())
.getConfiguration()),
"in sh_library rule //foo:foo: target '//bar:bar' is not visible from target "
+ "'//foo:foo'. Check the visibility declaration of the former target if you "
+ "think the dependency is legitimate"));
}
@Test
public void testFileVisibilityError() throws Exception {
scratch.file("foo/BUILD", "sh_library(name = 'foo', srcs = ['//bar:bar.sh'])");
scratch.file("bar/BUILD", "exports_files(['bar.sh'], visibility = ['//visibility:private'])");
scratch.file("bar/bar.sh");
AnalysisResult result = update(eventBus, defaultFlags().with(Flag.KEEP_GOING), "//foo");
assertThat(result.hasError()).isTrue();
Label topLevel = Label.parseAbsoluteUnchecked("//foo");
assertThat(collector.events)
.valuesForKey(topLevel)
.containsExactly(
new AnalysisFailedCause(
Label.parseAbsolute("//foo", ImmutableMap.of()),
toId(
Iterables.getOnlyElement(result.getTopLevelTargetsWithConfigs())
.getConfiguration()),
"in sh_library rule //foo:foo: target '//bar:bar.sh' is not visible from target "
+ "'//foo:foo'. Check the visibility declaration of the former target if you "
+ "think the dependency is legitimate. To set the visibility of that source "
+ "file target, use the exports_files() function"));
}
@Test
public void testVisibilityErrorNoKeepGoing() throws Exception {
scratch.file("foo/BUILD",
"sh_library(name = 'foo', deps = ['//bar'])");
scratch.file("bar/BUILD",
"sh_library(name = 'bar', visibility = ['//visibility:private'])");
try {
update(eventBus, defaultFlags(), "//foo");
} catch (ViewCreationFailedException e) {
// Ignored; we check for the correct eventbus event below.
}
Label topLevel = Label.parseAbsoluteUnchecked("//foo");
BuildConfiguration expectedConfig =
Iterables.getOnlyElement(
skyframeExecutor
.getSkyframeBuildView()
.getBuildConfigurationCollection().getTargetConfigurations());
assertThat(collector.events.get(topLevel))
.containsExactly(
new AnalysisFailedCause(
Label.parseAbsolute("//foo", ImmutableMap.of()),
toId(expectedConfig),
"in sh_library rule //foo:foo: target '//bar:bar' is not visible from target "
+ "'//foo:foo'. Check the visibility declaration of the former target if you "
+ "think the dependency is legitimate"));
}
// TODO(ulfjack): Add more tests for
// - a target that has multiple analysis errors (in the target itself)
// - a visibility error in a dependency (not in the target itself)
// - an error in a config condition
// - a missing top-level target (does that even get this far?)
// - a top-level target with an InvalidConfigurationException
// - a top-level target with a ToolchainContextException
// - a top-level target with a visibility attribute that points to a non-package_group
// - a top-level target with a package_group that refers to a non-package_group
// - aspect errors
/** Class to collect analysis failures. */
public static class AnalysisFailureEventCollector {
private final Multimap<Label, Cause> events = HashMultimap.create();
Multimap<Label, Cause> causesByLabel() {
Multimap<Label, Cause> result = HashMultimap.create();
return result;
}
@Subscribe
public void failureEvent(AnalysisFailureEvent event) {
events.putAll(event.getFailedTarget().getLabel(), event.getRootCauses().toList());
}
}
}