blob: 73ade83bc7c1c21c3444c5bba300b9f054581d2b [file] [log] [blame]
// 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.pkgcache;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.MoreCollectors;
import com.google.devtools.build.lib.actions.ActionKeyContext;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.events.ExtendedEventHandler.Postable;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.ConstantRuleVisibility;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.StarlarkSemanticsOptions;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.util.MockToolsConfig;
import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
import com.google.devtools.build.lib.skyframe.PatternExpandingError;
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.TargetPatternPhaseValue;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.testutil.SkyframeExecutorTestHelper;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.common.options.Options;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.OptionsParsingException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link SkyframeExecutor#loadTargetPatternsWithFilters}. */
@RunWith(JUnit4.class)
public class LoadingPhaseRunnerTest {
private static final ImmutableList<Logger> loggers = ImmutableList.of(
Logger.getLogger(BuildView.class.getName()));
static {
for (Logger logger : loggers) {
logger.setLevel(Level.OFF);
}
}
private LoadingPhaseTester tester;
@Before
public final void createLoadingPhaseTester() throws Exception {
tester = new LoadingPhaseTester();
}
private List<Label> getLabels(String... labels) throws Exception {
List<Label> result = new ArrayList<>();
for (String label : labels) {
result.add(Label.parseAbsoluteUnchecked(label));
}
return result;
}
private void assertCircularSymlinksDuringTargetParsing(String targetPattern) throws Exception {
assertThrows(TargetParsingException.class, () -> tester.load(targetPattern));
tester.assertContainsError("circular symlinks detected");
TargetPatternPhaseValue result = tester.loadKeepGoing(targetPattern);
assertThat(result.hasError()).isTrue();
}
private TargetPatternPhaseValue assertNoErrors(TargetPatternPhaseValue loadingResult) {
assertThat(loadingResult.hasError()).isFalse();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
tester.assertNoEvents();
return loadingResult;
}
@Test
public void testSmoke() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
TargetPatternPhaseValue loadingResult = assertNoErrors(tester.load("//base:hello"));
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//base:hello"));
assertThat(loadingResult.getTestsToRunLabels()).isNull();
}
@Test
public void testNonExistentPackage() throws Exception {
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//base:missing");
assertThat(loadingResult.hasError()).isTrue();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
assertThat(loadingResult.getTargetLabels()).isEmpty();
assertThat(loadingResult.getTestsToRunLabels()).isNull();
tester.assertContainsError("Skipping '//base:missing': no such package 'base'");
tester.assertContainsWarning("Target pattern parsing failed.");
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//base:missing");
}
@Test
public void testNonExistentPackageWithoutKeepGoing() throws Exception {
assertThrows(TargetParsingException.class, () -> tester.load("//does/not/exist"));
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//does/not/exist");
}
@Test
public void testNonExistentTarget() throws Exception {
tester.addFile("base/BUILD");
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//base:missing");
assertThat(loadingResult.hasError()).isTrue();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
assertThat(loadingResult.getTargetLabels()).isEmpty();
assertThat(loadingResult.getTestsToRunLabels()).isNull();
tester.assertContainsError("Skipping '//base:missing': no such target '//base:missing'");
tester.assertContainsWarning("Target pattern parsing failed.");
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//base:missing");
}
@Test
public void testExistingAndNonExistentTargetsWithKeepGoing() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
tester.loadKeepGoing("//base:hello", "//base:missing");
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//base:missing");
TargetParsingCompleteEvent event = tester.findPostOnce(TargetParsingCompleteEvent.class);
assertThat(event.getOriginalTargetPattern()).containsExactly("//base:hello", "//base:missing");
assertThat(event.getFailedTargetPatterns()).containsExactly("//base:missing");
}
@Test
public void testRecursiveAllRules() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'base', srcs = ['base.txt'])");
tester.addFile("base/foo/BUILD",
"filegroup(name = 'foo', srcs = ['foo.txt'])");
tester.addFile("base/bar/BUILD",
"filegroup(name = 'bar', srcs = ['bar.txt'])");
TargetPatternPhaseValue loadingResult = tester.load("//base/...");
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//base", "//base/foo", "//base/bar"));
loadingResult = tester.load("//base/bar/...");
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//base/bar"));
}
@Test
public void testRecursiveAllTargets() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'base', srcs = ['base.txt'])");
tester.addFile("base/foo/BUILD",
"filegroup(name = 'foo', srcs = ['foo.txt'])");
tester.addFile("base/bar/BUILD",
"filegroup(name = 'bar', srcs = ['bar.txt'])");
TargetPatternPhaseValue loadingResult = tester.load("//base/...:*");
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(
getLabels(
"//base:BUILD",
"//base:base",
"//base:base.txt",
"//base/foo:BUILD",
"//base/foo:foo",
"//base/foo:foo.txt",
"//base/bar:BUILD",
"//base/bar:bar",
"//base/bar:bar.txt"));
loadingResult = tester.load("//base/...:all-targets");
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(
getLabels(
"//base:BUILD",
"//base:base",
"//base:base.txt",
"//base/foo:BUILD",
"//base/foo:foo",
"//base/foo:foo.txt",
"//base/bar:BUILD",
"//base/bar:bar",
"//base/bar:bar.txt"));
}
@Test
public void testNonExistentRecursive() throws Exception {
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//base/...");
assertThat(loadingResult.hasError()).isTrue();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
assertThat(loadingResult.getTargetLabels()).isEmpty();
assertThat(loadingResult.getTestsToRunLabels()).isNull();
tester.assertContainsError("Skipping '//base/...': no targets found beneath 'base'");
tester.assertContainsWarning("Target pattern parsing failed.");
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//base/...");
}
@Test
public void testMistypedTarget() throws Exception {
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load("foo//bar:missing"));
assertThat(e)
.hasMessageThat()
.contains(
"invalid target format 'foo//bar:missing': "
+ "invalid package name 'foo//bar': "
+ "package names may not contain '//' path separators");
ParsingFailedEvent err = tester.findPostOnce(ParsingFailedEvent.class);
assertThat(err.getPattern()).isEqualTo("foo//bar:missing");
}
@Test
public void testEmptyTarget() throws Exception {
TargetParsingException e = assertThrows(TargetParsingException.class, () -> tester.load(""));
assertThat(e).hasMessageThat().contains("the empty string is not a valid target");
}
@Test
public void testMistypedTargetKeepGoing() throws Exception {
TargetPatternPhaseValue result = tester.loadKeepGoing("foo//bar:missing");
assertThat(result.hasError()).isTrue();
tester.assertContainsError(
"invalid target format 'foo//bar:missing': "
+ "invalid package name 'foo//bar': "
+ "package names may not contain '//' path separators");
ParsingFailedEvent err = tester.findPostOnce(ParsingFailedEvent.class);
assertThat(err.getPattern()).isEqualTo("foo//bar:missing");
}
@Test
public void testBadTargetPatternWithTest() throws Exception {
tester.addFile("base/BUILD");
TargetPatternPhaseValue loadingResult = tester.loadTestsKeepGoing("//base:missing");
assertThat(loadingResult.hasError()).isTrue();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
assertThat(loadingResult.getTargetLabels()).isEmpty();
assertThat(loadingResult.getTestsToRunLabels()).isEmpty();
tester.assertContainsError("Skipping '//base:missing': no such target '//base:missing'");
tester.assertContainsWarning("Target pattern parsing failed.");
}
@Test
public void testManualTarget() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD", "cc_library(name = 'my_lib', srcs = ['lib.cc'], tags = ['manual'])");
TargetPatternPhaseValue loadingResult = assertNoErrors(tester.load("//cc:all"));
assertThat(loadingResult.getTargetLabels()).containsExactlyElementsIn(getLabels());
// Explicitly specified on the command line.
loadingResult = assertNoErrors(tester.load("//cc:my_lib"));
assertThat(loadingResult.getTargetLabels()).containsExactlyElementsIn(getLabels("//cc:my_lib"));
}
@Test
public void testConfigSettingTarget() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("config/BUILD",
"cc_library(name = 'somelib', srcs = [ 'somelib.cc' ], hdrs = [ 'somelib.h' ])",
"config_setting(name = 'configa', values = { 'define': 'foo=a' })",
"config_setting(name = 'configb', values = { 'define': 'foo=b' })");
TargetPatternPhaseValue result = assertNoErrors(tester.load("//config:all"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//config:somelib"));
// Explicitly specified on the command line.
result = assertNoErrors(tester.load("//config:configa"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//config:configa"));
}
@Test
public void testNegativeTestDoesNotShowUpAtAll() throws Exception {
tester.addFile("my_test/BUILD",
"sh_test(name = 'my_test', srcs = ['test.cc'])");
assertNoErrors(tester.loadTests("-//my_test"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testNegativeTargetDoesNotShowUpAtAll() throws Exception {
tester.addFile("my_library/BUILD",
"cc_library(name = 'my_library', srcs = ['test.cc'])");
assertNoErrors(tester.loadTests("-//my_library"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testTestMinusAllTests() throws Exception {
tester.addFile(
"test/BUILD",
"cc_library(name = 'bar1')",
"cc_test(name = 'test', deps = [':bar1'], tags = ['manual'])");
TargetPatternPhaseValue result = tester.loadTests("//test:test", "-//test:all");
assertThat(result.hasError()).isFalse();
assertThat(result.hasPostExpansionError()).isFalse();
tester.assertContainsWarning("All specified test targets were excluded by filters");
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getLabels("//test:test"));
assertThat(result.getTargetLabels()).isEmpty();
}
@Test
public void testFindLongestPrefix() throws Exception {
tester.addFile("base/BUILD", "exports_files(['bar', 'bar/bar', 'bar/baz'])");
TargetPatternPhaseValue result = assertNoErrors(tester.load("base/bar/baz"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:bar/baz"));
result = assertNoErrors(tester.load("base/bar"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:bar"));
}
@Test
public void testMultiSegmentLabel() throws Exception {
tester.addFile("base/foo/BUILD", "exports_files(['bar/baz'])");
TargetPatternPhaseValue value = assertNoErrors(tester.load("base/foo:bar/baz"));
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//base/foo:bar/baz"));
}
@Test
public void testMultiSegmentLabelRelative() throws Exception {
tester.addFile("base/foo/BUILD", "exports_files(['bar/baz'])");
tester.setRelativeWorkingDirectory("base");
TargetPatternPhaseValue value = assertNoErrors(tester.load("foo:bar/baz"));
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//base/foo:bar/baz"));
}
@Test
public void testDeletedPackage() throws Exception {
tester.addFile("base/BUILD", "exports_files(['base'])");
tester.setDeletedPackages(PackageIdentifier.createInMainRepo("base"));
TargetPatternPhaseValue result = tester.loadKeepGoing("//base");
assertThat(result.hasError()).isTrue();
tester.assertContainsError(
"no such package 'base': Package is considered deleted due to --deleted_packages");
ParsingFailedEvent err = tester.findPostOnce(ParsingFailedEvent.class);
assertThat(err.getPattern()).isEqualTo("//base");
}
private void writeBuildFilesForTestFiltering() throws Exception {
tester.addFile("tests/BUILD",
"sh_test(name = 't1', srcs = ['pass.sh'], size= 'small', local=1)",
"sh_test(name = 't2', srcs = ['pass.sh'], size = 'medium')",
"sh_test(name = 't3', srcs = ['pass.sh'], tags = ['manual', 'local'])");
}
@Test
public void testTestFiltering() throws Exception {
writeBuildFilesForTestFiltering();
TargetPatternPhaseValue loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(loadingResult.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testTestFilteringIncludingManual() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--build_manual_tests");
TargetPatternPhaseValue loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2", "//tests:t3"));
assertThat(loadingResult.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testTestFilteringBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--build_tests_only");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testTestFilteringSize() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_size_filters=small");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t2"));
assertThat(result.getTestsToRunLabels()).containsExactlyElementsIn(getLabels("//tests:t1"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).isEmpty();
}
@Test
public void testTestFilteringSizeAndBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_size_filters=small", "--build_tests_only");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//tests:t1"));
assertThat(result.getTestsToRunLabels()).containsExactlyElementsIn(getLabels("//tests:t1"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getLabels("//tests:t2"));
}
@Test
public void testTestFilteringLocalAndBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_tag_filters=local", "--build_tests_only");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//tests:all", "//tests:t3"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t3"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//tests:t1", "//tests:t3"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getLabels("//tests:t2"));
}
@Test
public void testTestSuiteExpansion() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"test_suite(name = 'tests', tests = [':my_test'])");
TargetPatternPhaseValue loadingResult = assertNoErrors(tester.loadTests("//cc:tests"));
assertThat(loadingResult.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:my_test"));
assertThat(loadingResult.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//cc:my_test"));
assertThat(tester.getOriginalTargets())
.containsExactlyElementsIn(getLabels("//cc:tests", "//cc:my_test"));
assertThat(tester.getTestSuiteTargets())
.containsExactly(Label.parseAbsoluteUnchecked("//cc:tests"));
}
@Test
public void testTestSuiteExpansionFails() throws Exception {
tester.addFile("ts/BUILD",
"test_suite(name = 'tests', tests = ['//nonexistent:my_test'])");
tester.useLoadingOptions("--build_tests_only");
TargetPatternPhaseValue loadingResult = tester.loadTestsKeepGoing("//ts:tests");
assertThat(loadingResult.hasError()).isTrue();
assertThat(loadingResult.hasPostExpansionError()).isFalse();
tester.assertContainsError("no such package 'nonexistent'");
}
@Test
public void testTestSuiteExpansionFailsForBuild() throws Exception {
tester.addFile("ts/BUILD",
"test_suite(name = 'tests', tests = [':nonexistent_test'])");
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//ts:tests");
assertThat(loadingResult.hasError()).isFalse();
assertThat(loadingResult.hasPostExpansionError()).isTrue();
tester.assertContainsError(
"expecting a test or a test_suite rule but '//ts:nonexistent_test' is not one");
}
@Test
public void failureWhileLoadingTestsForTestSuiteKeepGoing() throws Exception {
tester.addFile("ts/BUILD", "test_suite(name = 'tests', tests = ['//pkg:tests'])");
tester.addFile("pkg/BUILD", "test_suite(name = 'tests')", "test_suite()");
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//ts:tests");
assertThat(loadingResult.hasError()).isFalse();
assertThat(loadingResult.hasPostExpansionError()).isTrue();
tester.assertContainsError("test_suite rule has no 'name' attribute");
}
@Test
public void failureWhileLoadingTestsForTestSuiteNoKeepGoing() throws Exception {
tester.addFile("ts/BUILD", "test_suite(name = 'tests', tests = ['//pkg:tests'])");
tester.addFile("pkg/BUILD", "test_suite(name = 'tests')", "test_suite()");
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load("//ts:tests"));
assertThat(e)
.hasMessageThat()
.isEqualTo("error loading package 'pkg': Package 'pkg' contains errors");
tester.assertContainsError("test_suite rule has no 'name' attribute");
}
@Test
public void testTestSuiteExpansionFailsMissingTarget() throws Exception {
tester.addFile("other/BUILD", "");
tester.addFile("ts/BUILD",
"test_suite(name = 'tests', tests = ['//other:no_such_test'])");
TargetPatternPhaseValue result = tester.loadTestsKeepGoing("//ts:tests");
assertThat(result.hasError()).isTrue();
assertThat(result.hasPostExpansionError()).isTrue();
tester.assertContainsError("no such target '//other:no_such_test'");
}
@Test
public void testTestSuiteExpansionFailsMultipleSuites() throws Exception {
tester.addFile("other/BUILD", "");
tester.addFile("ts/BUILD",
"test_suite(name = 'a', tests = ['//other:no_such_test'])",
"test_suite(name = 'b', tests = [])");
TargetPatternPhaseValue result = tester.loadTestsKeepGoing("//ts:all");
assertThat(result.hasError()).isTrue();
assertThat(result.hasPostExpansionError()).isTrue();
tester.assertContainsError("no such target '//other:no_such_test'");
}
@Test
public void testTestSuiteOverridesManualWithBuildTestsOnly() throws Exception {
tester.addFile("foo/BUILD",
"sh_test(name = 'foo', srcs = ['foo.sh'], tags = ['manual'])",
"sh_test(name = 'bar', srcs = ['bar.sh'], tags = ['manual'])",
"sh_test(name = 'baz', srcs = ['baz.sh'])",
"test_suite(name = 'foo_suite', tests = [':foo', ':baz'])");
tester.useLoadingOptions("--build_tests_only");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//foo:all"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//foo:foo", "//foo:baz"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//foo:foo", "//foo:baz"));
assertThat(tester.getFilteredTargets()).isEmpty();
assertThat(tester.getTestFilteredTargets())
.containsExactlyElementsIn(getLabels("//foo:foo_suite"));
}
/** Regression test for bug: "subtracting tests from test doesn't work" */
@Test
public void testFilterNegativeTestFromTestSuite() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"cc_test(name = 'my_other_test', srcs = ['other_test.cc'])",
"test_suite(name = 'tests', tests = [':my_test', ':my_other_test'])");
TargetPatternPhaseValue result =
assertNoErrors(tester.loadTests("//cc:tests", "-//cc:my_test"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:my_other_test", "//cc:my_test"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//cc:my_other_test"));
}
/** Regression test for bug: "blaze doesn't seem to respect target subtractions" */
@Test
public void testNegativeTestSuiteExpanded() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"cc_test(name = 'my_other_test', srcs = ['other_test.cc'])",
"test_suite(name = 'tests', tests = [':my_test'])",
"test_suite(name = 'all_tests', tests = ['my_other_test'])");
TargetPatternPhaseValue result =
assertNoErrors(tester.loadTests("//cc:all_tests", "-//cc:tests"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:my_other_test"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//cc:my_other_test"));
}
@Test
public void testTestSuiteIsSubtracted() throws Exception {
// Test suites are expanded for each target pattern in sequence, not the whole set of target
// patterns after all the inclusions and exclusions are processed.
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"cc_test(name = 'my_other_test', srcs = ['other_test.cc'])",
"test_suite(name = 'tests', tests = [':my_test'])");
TargetPatternPhaseValue result =
assertNoErrors(tester.loadTests("//cc:all", "-//cc:tests"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:my_test", "//cc:my_other_test"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//cc:my_other_test"));
}
/** Regression test for bug: "blaze test "no targets found" warning now fatal" */
@Test
public void testNoTestsInRecursivePattern() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("foo/BUILD", "cc_library(name = 'foo', srcs = ['foo.cc'])");
TargetPatternPhaseValue result =
assertNoErrors(tester.loadTests("//foo/..."));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//foo"));
assertThat(result.getTestsToRunLabels()).isEmpty();
}
@Test
public void testComplexTestSuite() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'test1', srcs = ['test.cc'])",
"cc_test(name = 'test2', srcs = ['test.cc'])",
"test_suite(name = 'empty', tags = ['impossible'], tests = [])",
"test_suite(name = 'suite1', tests = ['empty', 'test1'])",
"test_suite(name = 'suite2', tests = ['test2'])",
"test_suite(name = 'all_tests', tests = ['suite1', 'suite2'])");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//cc:all_tests"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:test1", "//cc:test2"));
}
@Test
public void testAllExcludesManualTest() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"cc_test(name = 'my_other_test', srcs = ['other_test.cc'], tags = ['manual'])");
TargetPatternPhaseValue result = assertNoErrors(tester.loadTests("//cc:all"));
assertThat(result.getTargetLabels())
.containsExactlyElementsIn(getLabels("//cc:my_test"));
assertThat(result.getTestsToRunLabels())
.containsExactlyElementsIn(getLabels("//cc:my_test"));
}
/**
* Regression test for bug: "blaze is lying to me about what tests exist (have been specified)"
*/
@Test
public void testTotalNegationEmitsWarning() throws Exception {
AnalysisMock.get().ccSupport().setup(tester.mockToolsConfig);
tester.addFile("cc/BUILD",
"cc_test(name = 'my_test', srcs = ['test.cc'])",
"test_suite(name = 'tests', tests = [':my_test'])");
TargetPatternPhaseValue result = tester.loadTests("//cc:tests", "-//cc:my_test");
tester.assertContainsWarning("All specified test targets were excluded by filters");
assertThat(result.getTestsToRunLabels()).containsExactlyElementsIn(getLabels());
}
@Test
public void testRepeatedSameLoad() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
TargetPatternPhaseValue firstResult = assertNoErrors(tester.load("//base:hello"));
TargetPatternPhaseValue secondResult = assertNoErrors(tester.load("//base:hello"));
assertThat(secondResult.getTargetLabels()).isEqualTo(firstResult.getTargetLabels());
assertThat(secondResult.getTestsToRunLabels()).isEqualTo(firstResult.getTestsToRunLabels());
}
/**
* Tests whether globs can update correctly when a new file is added.
*
* <p>The usage of {@link LoadingPhaseTester#sync()} triggers this via
* {@link SkyframeExecutor#invalidateFilesUnderPathForTesting}.
*/
@Test
public void testGlobPicksUpNewFile() throws Exception {
tester.addFile("foo/BUILD", "filegroup(name='x', srcs=glob(['*.y']))");
tester.addFile("foo/a.y");
Label label =
Iterables.getOnlyElement(assertNoErrors(tester.load("//foo:x")).getTargetLabels());
Target result = tester.getTarget(label.toString());
assertThat(
Iterables.transform(result.getAssociatedRule().getLabels(), Functions.toStringFunction()))
.containsExactly("//foo:a.y");
tester.addFile("foo/b.y");
tester.sync();
label = Iterables.getOnlyElement(assertNoErrors(tester.load("//foo:x")).getTargetLabels());
result = tester.getTarget(label.toString());
assertThat(
Iterables.transform(result.getAssociatedRule().getLabels(), Functions.toStringFunction()))
.containsExactly("//foo:a.y", "//foo:b.y");
}
/** Regression test: handle symlink cycles gracefully. */
@Test
public void testCycleReporting_SymlinkCycleDuringTargetParsing() throws Exception {
tester.addFile("hello/BUILD", "cc_library(name = 'a', srcs = glob(['*.cc']))");
Path buildFilePath = tester.getWorkspace().getRelative("hello/BUILD");
Path dirPath = buildFilePath.getParentDirectory();
Path fooFilePath = dirPath.getRelative("foo.cc");
Path barFilePath = dirPath.getRelative("bar.cc");
Path bazFilePath = dirPath.getRelative("baz.cc");
fooFilePath.createSymbolicLink(barFilePath);
barFilePath.createSymbolicLink(bazFilePath);
bazFilePath.createSymbolicLink(fooFilePath);
assertCircularSymlinksDuringTargetParsing("//hello:a");
}
@Test
public void testRecursivePatternWithCircularSymlink() throws Exception {
tester.getWorkspace().getChild("broken").createDirectory();
// Create a circular symlink.
tester.getWorkspace().getRelative(PathFragment.create("broken/BUILD"))
.createSymbolicLink(PathFragment.create("BUILD"));
assertCircularSymlinksDuringTargetParsing("//broken/...");
}
@Test
public void testRecursivePatternWithTwoCircularSymlinks() throws Exception {
tester.getWorkspace().getChild("broken").createDirectory();
// Create a circular symlink.
tester.getWorkspace().getRelative(PathFragment.create("broken/BUILD"))
.createSymbolicLink(PathFragment.create("x"));
tester.getWorkspace().getRelative(PathFragment.create("broken/x"))
.createSymbolicLink(PathFragment.create("BUILD"));
assertCircularSymlinksDuringTargetParsing("//broken/...");
}
@Test
public void testSuiteInSuite() throws Exception {
tester.addFile("suite/BUILD",
"test_suite(name = 'a', tests = [':b'])",
"test_suite(name = 'b', tests = [':c'])",
"sh_test(name = 'c', srcs = ['test.cc'])");
TargetPatternPhaseValue result = assertNoErrors(tester.load("//suite:a"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//suite:c"));
}
@Test
public void testTopLevelTargetErrorsPrintedExactlyOnce_NoKeepGoing() throws Exception {
tester.addFile("bad/BUILD", "sh_binary(name = 'bad', srcs = ['bad.sh'])", "fail('some error')");
assertThrows(TargetParsingException.class, () -> tester.load("//bad"));
tester.assertContainsEventWithFrequency("some error", 1);
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//bad");
}
@Test
public void testTopLevelTargetErrorsPrintedExactlyOnce_KeepGoing() throws Exception {
tester.addFile("bad/BUILD", "sh_binary(name = 'bad', srcs = ['bad.sh'])", "fail('some error')");
TargetPatternPhaseValue result = tester.loadKeepGoing("//bad");
assertThat(result.hasError()).isTrue();
tester.assertContainsEventWithFrequency("some error", 1);
}
@Test
public void testCompileOneDependency() throws Exception {
tester.addFile("base/BUILD",
"cc_library(name = 'hello', srcs = ['hello.cc'])");
tester.useLoadingOptions("--compile_one_dependency");
TargetPatternPhaseValue result = assertNoErrors(tester.load("base/hello.cc"));
assertThat(result.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:hello"));
}
@Test
public void testCompileOneDependencyNonExistentSource() throws Exception {
tester.addFile("base/BUILD",
"cc_library(name = 'hello', srcs = ['hello.cc', '//bad:bad.cc'])");
tester.useLoadingOptions("--compile_one_dependency");
try {
TargetPatternPhaseValue loadingResult = tester.load("base/hello.cc");
assertThat(loadingResult.hasPostExpansionError()).isFalse();
} catch (LoadingFailedException expected) {
tester.assertContainsError("no such package 'bad'");
}
}
@Test
public void testCompileOneDependencyNonExistentSourceKeepGoing() throws Exception {
tester.addFile("base/BUILD",
"cc_library(name = 'hello', srcs = ['hello.cc', '//bad:bad.cc'])");
tester.useLoadingOptions("--compile_one_dependency");
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("base/hello.cc");
assertThat(loadingResult.hasPostExpansionError()).isFalse();
}
@Test
public void testCompileOneDependencyReferencesFile() throws Exception {
tester.addFile("base/BUILD",
"cc_library(name = 'hello', srcs = ['hello.cc', '//bad:bad.cc'])");
tester.useLoadingOptions("--compile_one_dependency");
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load("//base:hello"));
assertThat(e)
.hasMessageThat()
.contains("--compile_one_dependency target '//base:hello' must be a file");
}
@Test
public void testParsingFailureReported() throws Exception {
TargetPatternPhaseValue loadingResult = tester.loadKeepGoing("//does_not_exist");
assertThat(loadingResult.hasError()).isTrue();
ParsingFailedEvent event = tester.findPostOnce(ParsingFailedEvent.class);
assertThat(event.getPattern()).isEqualTo("//does_not_exist");
assertThat(event.getMessage()).contains("BUILD file not found");
}
@Test
public void testCyclesKeepGoing() throws Exception {
tester.addFile("test/BUILD", "load(':cycle1.bzl', 'make_cycle')");
tester.addFile("test/cycle1.bzl", "load(':cycle2.bzl', 'make_cycle')");
tester.addFile("test/cycle2.bzl", "load(':cycle1.bzl', 'make_cycle')");
// The skyframe target pattern evaluator isn't able to provide partial results in the presence
// of cycles, so it simply raises an exception rather than returning an empty result.
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load("//test:cycle1"));
assertThat(e).hasMessageThat().contains("cycles detected");
tester.assertContainsEventWithFrequency("cycle detected in extension", 1);
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//test:cycle1");
}
@Test
public void testCyclesNoKeepGoing() throws Exception {
tester.addFile("test/BUILD", "load(':cycle1.bzl', 'make_cycle')");
tester.addFile("test/cycle1.bzl", "load(':cycle2.bzl', 'make_cycle')");
tester.addFile("test/cycle2.bzl", "load(':cycle1.bzl', 'make_cycle')");
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load("//test:cycle1"));
assertThat(e).hasMessageThat().contains("cycles detected");
tester.assertContainsEventWithFrequency("cycle detected in extension", 1);
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//test:cycle1");
}
@Test
public void mapsOriginalPatternsToLabels() throws Exception {
tester.addFile("test/a/BUILD", "cc_library(name = 'a_lib', srcs = ['a.cc'])");
tester.addFile("test/b/BUILD", "cc_library(name = 'b_lib', srcs = ['b.cc'])");
tester.load("test/a:all", "test/b:all", "test/...");
assertThat(tester.getOriginalPatternsToLabels())
.containsExactly(
"test/a:all", Label.parseAbsoluteUnchecked("//test/a:a_lib"),
"test/b:all", Label.parseAbsoluteUnchecked("//test/b:b_lib"),
"test/...", Label.parseAbsoluteUnchecked("//test/a:a_lib"),
"test/...", Label.parseAbsoluteUnchecked("//test/b:b_lib"));
}
@Test
public void testSuiteCycle() throws Exception {
tester.addFile(
"BUILD", "test_suite(name = 'a', tests = [':b']); test_suite(name = 'b', tests = [':a'])");
assertThat(
assertThrows(TargetParsingException.class, () -> tester.loadKeepGoing("//:a", "//:b")))
.hasMessageThat()
.contains("cycles detected");
assertThat(tester.assertContainsError("cycle in dependency graph").toString())
.containsMatch("in test_suite rule //:.: cycle in dependency graph");
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//:a", "//:b");
}
@Test
public void mapsOriginalPatternsToLabels_omitsExcludedTargets() throws Exception {
tester.addFile("test/a/BUILD", "cc_library(name = 'a_lib', srcs = ['a.cc'])");
tester.load("test/...", "-test/a:a_lib");
assertThat(tester.getOriginalPatternsToLabels()).isEmpty();
}
@Test
public void testWildcard() throws Exception {
tester.addFile("foo/lib/BUILD",
"sh_library(name = 'lib2', srcs = ['foo.cc'])");
TargetPatternPhaseValue value = assertNoErrors(tester.load("//foo/lib:all-targets"));
assertThat(value.getTargetLabels())
.containsExactlyElementsIn(
getLabels("//foo/lib:BUILD", "//foo/lib:lib2", "//foo/lib:foo.cc"));
value = assertNoErrors(tester.load("//foo/lib:*"));
assertThat(value.getTargetLabels())
.containsExactlyElementsIn(
getLabels("//foo/lib:BUILD", "//foo/lib:lib2", "//foo/lib:foo.cc"));
}
@Test
public void testWildcardConflict() throws Exception {
tester.addFile("foo/lib/BUILD",
"cc_library(name = 'lib1')",
"cc_library(name = 'lib2')",
"cc_library(name = 'all-targets')",
"cc_library(name = 'all')");
assertWildcardConflict("//foo/lib:all", ":all");
assertWildcardConflict("//foo/lib:all-targets", ":all-targets");
}
private void assertWildcardConflict(String label, String suffix) throws Exception {
TargetPatternPhaseValue value = tester.load(label);
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels(label));
tester.assertContainsWarning(String.format("The target pattern '%s' is ambiguous: '%s' is both "
+ "a wildcard, and the name of an existing cc_library rule; "
+ "using the latter interpretation", label, suffix));
}
@Test
public void testAbsolutePatternEndsWithSlashAll() throws Exception {
tester.addFile("foo/all/BUILD", "cc_library(name = 'all')");
TargetPatternPhaseValue value = tester.load("//foo/all");
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//foo/all:all"));
}
@Test
public void testRelativeLabel() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
TargetPatternPhaseValue value = assertNoErrors(tester.load("base:hello"));
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:hello"));
}
@Test
public void testAbsoluteLabelWithOffset() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
tester.setRelativeWorkingDirectory("base");
TargetPatternPhaseValue value = assertNoErrors(tester.load("//base:hello"));
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:hello"));
}
@Test
public void testRelativeLabelWithOffset() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
tester.setRelativeWorkingDirectory("base");
TargetPatternPhaseValue value = assertNoErrors(tester.load(":hello"));
assertThat(value.getTargetLabels()).containsExactlyElementsIn(getLabels("//base:hello"));
}
private void expectError(String pattern, String message) throws Exception {
TargetParsingException e =
assertThrows(TargetParsingException.class, () -> tester.load(pattern));
assertThat(e).hasMessageThat().contains(message);
}
@Test
public void testPatternWithSingleSlashIsError() throws Exception {
expectError(
"/single/slash",
"not a valid absolute pattern (absolute target patterns must start with exactly "
+ "two slashes): '/single/slash'");
}
@Test
public void testPatternWithSingleSlashIsErrorAndOffset() throws Exception {
tester.setRelativeWorkingDirectory("base");
expectError(
"/single/slash",
"not a valid absolute pattern (absolute target patterns must start with exactly "
+ "two slashes): '/single/slash'");
}
@Test
public void testPatternWithTripleSlashIsError() throws Exception {
expectError(
"///triple/slash",
"not a valid absolute pattern (absolute target patterns must start with exactly "
+ "two slashes): '///triple/slash'");
}
@Test
public void testPatternEndingWithSingleSlashIsError() throws Exception {
expectError(
"foo/",
"The package part of 'foo/' should not end in a slash");
}
@Test
public void testPatternStartingWithDotDotSlash() throws Exception {
expectError(
"../foo",
"Bad target pattern '../foo': package name component contains only '.' characters");
}
private void runTestPackageLoadingError(boolean keepGoing, String... patterns) throws Exception {
tester.addFile("bad/BUILD", "nope");
if (keepGoing) {
TargetPatternPhaseValue value = tester.loadKeepGoing(patterns);
assertThat(value.hasError()).isTrue();
tester.assertContainsWarning("Target pattern parsing failed");
} else {
TargetParsingException exn =
assertThrows(TargetParsingException.class, () -> tester.load(patterns));
assertThat(exn).hasCauseThat().isInstanceOf(BuildFileContainsErrorsException.class);
assertThat(exn).hasCauseThat().hasMessageThat().contains("Package 'bad' contains errors");
}
tester.assertContainsError("/workspace/bad/BUILD:1:1: name 'nope' is not defined");
}
@Test
public void testPackageLoadingError_KeepGoing_ExplicitTarget() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ true, "//bad:BUILD");
}
@Test
public void testPackageLoadingError_NoKeepGoing_ExplicitTarget() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ false, "//bad:BUILD");
}
@Test
public void testPackageLoadingError_KeepGoing_TargetsInPackage() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ true, "//bad:all");
}
@Test
public void testPackageLoadingError_NoKeepGoing_TargetsInPackage() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ false, "//bad:all");
}
@Test
public void testPackageLoadingError_KeepGoing_TargetsBeneathDirectory() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ true, "//bad/...");
}
@Test
public void testPackageLoadingError_NoKeepGoing_TargetsBeneathDirectory() throws Exception {
runTestPackageLoadingError(/*keepGoing=*/ false, "//bad/...");
}
@Test
public void testPackageLoadingError_KeepGoing_SomeGoodTargetsBeneathDirectory() throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestPackageLoadingError(/*keepGoing=*/ true, "//...");
}
@Test
public void testPackageLoadingError_NoKeepGoing_SomeGoodTargetsBeneathDirectory()
throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestPackageLoadingError(/*keepGoing=*/ false, "//...");
}
private void runTestPackageFileInconsistencyError(boolean keepGoing, String... patterns)
throws Exception {
tester.addFile("bad/BUILD", "sh_library(name = 't')\n");
IOException ioExn = new IOException("nope");
tester.throwExceptionOnGetInputStream(tester.getWorkspace().getRelative("bad/BUILD"), ioExn);
if (keepGoing) {
TargetPatternPhaseValue value = tester.loadKeepGoing(patterns);
assertThat(value.hasError()).isTrue();
tester.assertContainsWarning("Target pattern parsing failed");
tester.assertContainsError("error loading package 'bad': nope");
} else {
TargetParsingException exn =
assertThrows(TargetParsingException.class, () -> tester.load(patterns));
assertThat(exn).hasCauseThat().isInstanceOf(BuildFileContainsErrorsException.class);
assertThat(exn).hasCauseThat().hasMessageThat().contains("error loading package 'bad': nope");
}
}
@Test
public void testPackageFileInconsistencyError_KeepGoing_ExplicitTarget() throws Exception {
runTestPackageFileInconsistencyError(true, "//bad:BUILD");
}
@Test
public void testPackageFileInconsistencyError_NoKeepGoing_ExplicitTarget() throws Exception {
runTestPackageFileInconsistencyError(false, "//bad:BUILD");
}
@Test
public void testPackageFileInconsistencyError_KeepGoing_TargetsInPackage() throws Exception {
runTestPackageFileInconsistencyError(true, "//bad:all");
}
@Test
public void testPackageFileInconsistencyError_NoKeepGoing_TargetsInPackage() throws Exception {
runTestPackageFileInconsistencyError(false, "//bad:all");
}
@Test
public void testPackageFileInconsistencyError_KeepGoing_argetsBeneathDirectory()
throws Exception {
runTestPackageFileInconsistencyError(true, "//bad/...");
}
@Test
public void testPackageFileInconsistencyError_NoKeepGoing_TargetsBeneathDirectory()
throws Exception {
runTestPackageFileInconsistencyError(false, "//bad/...");
}
@Test
public void testPackageFileInconsistencyError_KeepGoing_SomeGoodTargetsBeneathDirectory()
throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestPackageFileInconsistencyError(true, "//...");
}
@Test
public void testPackageFileInconsistencyError_NoKeepGoing_SomeGoodTargetsBeneathDirectory()
throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestPackageFileInconsistencyError(false, "//...");
}
private void runTestExtensionLoadingError(boolean keepGoing, String... patterns)
throws Exception {
tester.addFile("bad/f1.bzl", "nope");
tester.addFile("bad/BUILD", "load(\":f1.bzl\", \"not_a_symbol\")");
if (keepGoing) {
TargetPatternPhaseValue value = tester.loadKeepGoing(patterns);
assertThat(value.hasError()).isTrue();
tester.assertContainsWarning("Target pattern parsing failed");
} else {
TargetParsingException exn =
assertThrows(TargetParsingException.class, () -> tester.load(patterns));
assertThat(exn).hasCauseThat().isInstanceOf(BuildFileContainsErrorsException.class);
assertThat(exn).hasCauseThat().hasMessageThat().contains("Extension 'bad/f1.bzl' has errors");
}
tester.assertContainsError("/workspace/bad/f1.bzl:1:1: name 'nope' is not defined");
}
@Test
public void testExtensionLoadingError_KeepGoing_ExplicitTarget() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ true, "//bad:BUILD");
}
@Test
public void testExtensionLoadingError_NoKeepGoing_ExplicitTarget() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ false, "//bad:BUILD");
}
@Test
public void testExtensionLoadingError_KeepGoing_TargetsInPackage() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ true, "//bad:all");
}
@Test
public void testExtensionLoadingError_NoKeepGoing_TargetsInPackage() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ false, "//bad:all");
}
@Test
public void testExtensionLoadingError_KeepGoing_TargetsBeneathDirectory() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ true, "//bad/...");
}
@Test
public void testExtensionLoadingError_NoKeepGoing_TargetsBeneathDirectory() throws Exception {
runTestExtensionLoadingError(/*keepGoing=*/ false, "//bad/...");
}
@Test
public void testExtensionLoadingError_KeepGoing_SomeGoodTargetsBeneathDirectory()
throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestExtensionLoadingError(/*keepGoing=*/ true, "//...");
}
@Test
public void testExtensionLoadingError_NoKeepGoing_SomeGoodTargetsBeneathDirectory()
throws Exception {
tester.addFile("good/BUILD", "sh_library(name = 't')\n");
runTestExtensionLoadingError(/*keepGoing=*/ false, "//...");
}
private static class LoadingPhaseTester {
private final ManualClock clock = new ManualClock();
private final CustomInMemoryFs fs = new CustomInMemoryFs(clock);
private final Path workspace;
private final AnalysisMock analysisMock;
private final SkyframeExecutor skyframeExecutor;
private final List<Path> changes = new ArrayList<>();
private final BlazeDirectories directories;
private final ActionKeyContext actionKeyContext = new ActionKeyContext();
private LoadingOptions options;
private final StoredEventHandler storedErrors;
private PathFragment relativeWorkingDirectory = PathFragment.EMPTY_FRAGMENT;
private TargetParsingCompleteEvent targetParsingCompleteEvent;
private LoadingPhaseCompleteEvent loadingPhaseCompleteEvent;
private MockToolsConfig mockToolsConfig;
public LoadingPhaseTester() throws IOException {
this.workspace = fs.getPath("/workspace");
workspace.createDirectory();
mockToolsConfig = new MockToolsConfig(workspace);
analysisMock = AnalysisMock.get();
analysisMock.setupMockClient(mockToolsConfig);
directories =
new BlazeDirectories(
new ServerDirectories(
fs.getPath("/install"), fs.getPath("/output"), fs.getPath("/userRoot")),
workspace,
/* defaultSystemJavabase= */ null,
analysisMock.getProductName());
workspace.getRelative("base").deleteTree();
ConfiguredRuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider();
PackageFactory pkgFactory =
analysisMock.getPackageFactoryBuilderForTesting(directories).build(ruleClassProvider, fs);
PackageCacheOptions options = Options.getDefaults(PackageCacheOptions.class);
storedErrors = new StoredEventHandler();
BuildOptions defaultBuildOptions;
try {
defaultBuildOptions = BuildOptions.of(ImmutableList.of());
} catch (OptionsParsingException e) {
throw new RuntimeException(e);
}
skyframeExecutor =
BazelSkyframeExecutorConstants.newBazelSkyframeExecutorBuilder()
.setPkgFactory(pkgFactory)
.setFileSystem(fs)
.setDirectories(directories)
.setActionKeyContext(actionKeyContext)
.setDefaultBuildOptions(defaultBuildOptions)
.setExtraSkyFunctions(analysisMock.getSkyFunctions(directories))
.build();
SkyframeExecutorTestHelper.process(skyframeExecutor);
PathPackageLocator pkgLocator =
PathPackageLocator.create(
null,
options.packagePath,
storedErrors,
workspace,
workspace,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY);
PackageCacheOptions packageCacheOptions = Options.getDefaults(PackageCacheOptions.class);
packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PRIVATE;
packageCacheOptions.showLoadingProgress = true;
packageCacheOptions.globbingThreads = 7;
skyframeExecutor.injectExtraPrecomputedValues(
ImmutableList.of(
PrecomputedValue.injected(
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE,
Optional.<RootedPath>absent())));
skyframeExecutor.preparePackageLoading(
pkgLocator,
packageCacheOptions,
Options.getDefaults(StarlarkSemanticsOptions.class),
UUID.randomUUID(),
ImmutableMap.<String, String>of(),
new TimestampGranularityMonitor(clock));
skyframeExecutor.setActionEnv(ImmutableMap.<String, String>of());
this.options = Options.getDefaults(LoadingOptions.class);
}
public void useLoadingOptions(String... options) throws OptionsParsingException {
OptionsParser parser = OptionsParser.builder().optionsClasses(LoadingOptions.class).build();
parser.parse(ImmutableList.copyOf(options));
this.options = parser.getOptions(LoadingOptions.class);
}
public void setRelativeWorkingDirectory(String relativeWorkingDirectory) {
this.relativeWorkingDirectory = PathFragment.create(relativeWorkingDirectory);
}
public void setDeletedPackages(PackageIdentifier... packages) {
skyframeExecutor.setDeletedPackages(ImmutableList.copyOf(packages));
}
public TargetPatternPhaseValue load(String... patterns) throws Exception {
return loadWithFlags(/*keepGoing=*/false, /*determineTests=*/false, patterns);
}
public TargetPatternPhaseValue loadKeepGoing(String... patterns) throws Exception {
return loadWithFlags(/*keepGoing=*/true, /*determineTests=*/false, patterns);
}
public TargetPatternPhaseValue loadTests(String... patterns) throws Exception {
return loadWithFlags(/*keepGoing=*/false, /*determineTests=*/true, patterns);
}
public TargetPatternPhaseValue loadTestsKeepGoing(String... patterns) throws Exception {
return loadWithFlags(/*keepGoing=*/true, /*determineTests=*/true, patterns);
}
public TargetPatternPhaseValue loadWithFlags(
boolean keepGoing, boolean determineTests, String... patterns) throws Exception {
sync();
storedErrors.clear();
TargetPatternPhaseValue result =
skyframeExecutor.loadTargetPatternsWithFilters(
storedErrors,
ImmutableList.copyOf(patterns),
relativeWorkingDirectory,
options,
// We load very few packages, and everything is in memory; two should be plenty.
/* threadCount= */ 2,
keepGoing,
determineTests);
this.targetParsingCompleteEvent = findPost(TargetParsingCompleteEvent.class);
this.loadingPhaseCompleteEvent = findPost(LoadingPhaseCompleteEvent.class);
if (!keepGoing) {
assertThat(storedErrors.hasErrors()).isFalse();
}
return result;
}
public Path getWorkspace() {
return workspace;
}
public void addFile(String fileName, String... content) throws IOException {
Path buildFile = workspace.getRelative(fileName);
Preconditions.checkState(!buildFile.exists());
Path currentPath = buildFile;
// Add the new file and all the directories that will be created by
// createDirectoryAndParents()
while (!currentPath.exists()) {
changes.add(currentPath);
currentPath = currentPath.getParentDirectory();
}
buildFile.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(buildFile, Joiner.on('\n').join(content));
}
private void sync() throws InterruptedException {
clock.advanceMillis(1);
ModifiedFileSet.Builder builder = ModifiedFileSet.builder();
for (Path path : changes) {
if (!path.startsWith(workspace)) {
continue;
}
PathFragment workspacePath = path.relativeTo(workspace);
builder.modify(workspacePath);
}
ModifiedFileSet modified = builder.build();
skyframeExecutor.invalidateFilesUnderPathForTesting(
storedErrors, modified, Root.fromPath(workspace));
changes.clear();
}
public Target getTarget(String targetName) throws Exception {
StoredEventHandler eventHandler = new StoredEventHandler();
Target target = getPkgManager().getTarget(
eventHandler, Label.parseAbsoluteUnchecked(targetName));
assertThat(eventHandler.hasErrors()).isFalse();
return target;
}
private PackageManager getPkgManager() {
return skyframeExecutor.getPackageManager();
}
public ImmutableSet<Label> getFilteredTargets() {
return ImmutableSet.copyOf(targetParsingCompleteEvent.getFilteredLabels());
}
public ImmutableSet<Label> getTestFilteredTargets() {
return ImmutableSet.copyOf(targetParsingCompleteEvent.getTestFilteredLabels());
}
public ImmutableSet<Label> getOriginalTargets() {
return ImmutableSet.copyOf(targetParsingCompleteEvent.getLabels());
}
public ImmutableSetMultimap<String, Label> getOriginalPatternsToLabels() {
return targetParsingCompleteEvent.getOriginalPatternsToLabels();
}
public ImmutableSet<Label> getTestSuiteTargets() {
return loadingPhaseCompleteEvent.getFilteredLabels();
}
void throwExceptionOnGetInputStream(Path path, IOException exn) {
fs.throwExceptionOnGetInputStream(path, exn);
}
private Iterable<Event> filteredEvents() {
return Iterables.filter(storedErrors.getEvents(), new Predicate<Event>() {
@Override
public boolean apply(Event event) {
return event.getKind() != EventKind.PROGRESS;
}
});
}
public void assertNoEvents() {
MoreAsserts.assertNoEvents(filteredEvents());
}
public Event assertContainsWarning(String expectedMessage) {
return MoreAsserts.assertContainsEvent(filteredEvents(), expectedMessage, EventKind.WARNING);
}
public Event assertContainsError(String expectedMessage) {
return MoreAsserts.assertContainsEvent(filteredEvents(), expectedMessage, EventKind.ERRORS);
}
public void assertContainsEventWithFrequency(String expectedMessage, int expectedFrequency) {
MoreAsserts.assertContainsEventWithFrequency(
filteredEvents(), expectedMessage, expectedFrequency);
}
public <T extends Postable> T findPost(Class<T> clazz) {
return Iterators.getNext(
storedErrors.getPosts().stream().filter(clazz::isInstance).map(clazz::cast).iterator(),
null);
}
public <T extends Postable> T findPostOnce(Class<T> clazz) {
return storedErrors
.getPosts()
.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(MoreCollectors.onlyElement());
}
}
/**
* Custom {@link InMemoryFileSystem} that can be pre-configured per-file to throw a supplied
* IOException instead of the usual behavior.
*/
private static class CustomInMemoryFs extends InMemoryFileSystem {
private final Map<Path, IOException> pathsToErrorOnGetInputStream = Maps.newHashMap();
CustomInMemoryFs(ManualClock manualClock) {
super(manualClock);
}
synchronized void throwExceptionOnGetInputStream(Path path, IOException exn) {
pathsToErrorOnGetInputStream.put(path, exn);
}
@Override
protected synchronized InputStream getInputStream(Path path) throws IOException {
IOException exnToThrow = pathsToErrorOnGetInputStream.get(path);
if (exnToThrow != null) {
throw exnToThrow;
}
return super.getInputStream(path);
}
}
}