blob: b4fa688e84b1dff2c186ab88d707c3bbd2f89058 [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 org.junit.Assert.fail;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
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.Iterables;
import com.google.common.collect.Iterators;
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.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.ConstantRuleVisibility;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.SkylarkSemanticsOptions;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.util.MockToolsConfig;
import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
import com.google.devtools.build.lib.skyframe.DiffAwareness;
import com.google.devtools.build.lib.skyframe.PatternExpandingError;
import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor;
import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.MoreAsserts;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystem;
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.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.util.ArrayList;
import java.util.Collection;
import java.util.List;
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 LoadingPhaseRunner}. */
@RunWith(JUnit4.class)
public class LoadingPhaseRunnerTest {
private static final ImmutableList<Logger> loggers = ImmutableList.of(
Logger.getLogger(LoadingPhaseRunner.class.getName()),
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(useSkyframeTargetPatternEval());
}
protected List<Target> getTargets(String... targetNames) throws Exception {
return tester.getTargets(targetNames);
}
protected boolean useSkyframeTargetPatternEval() {
return false;
}
@Test
public void testSmoke() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
LoadingResult loadingResult = assertNoErrors(tester.load("//base:hello"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//base:hello"));
assertThat(loadingResult.getTestsToRun()).isNull();
}
@Test
public void testSmokeWithCallback() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
final List<Target> targetsNotified = new ArrayList<>();
tester.setCallback(new LoadingCallback() {
@Override
public void notifyTargets(Collection<Target> targets) throws LoadingFailedException {
targetsNotified.addAll(targets);
}
});
assertNoErrors(tester.load("//base:hello"));
assertThat(targetsNotified).containsExactlyElementsIn(getTargets("//base:hello"));
}
@Test
public void testNonExistentPackage() throws Exception {
LoadingResult loadingResult = tester.loadKeepGoing("//base:missing");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).isFalse();
assertThat(loadingResult.getTargets()).isEmpty();
assertThat(loadingResult.getTestsToRun()).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");
assertThat(err.getSkipped()).isTrue();
}
@Test
public void testNonExistentTarget() throws Exception {
tester.addFile("base/BUILD");
LoadingResult loadingResult = tester.loadKeepGoing("//base:missing");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).isFalse();
assertThat(loadingResult.getTargets()).isEmpty();
assertThat(loadingResult.getTestsToRun()).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");
assertThat(err.getSkipped()).isTrue();
}
@Test
public void testMistypedTarget() throws Exception {
try {
tester.load("foo//bar:missing");
fail();
} catch (TargetParsingException e) {
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 {
try {
tester.load("");
fail();
} catch (TargetParsingException e) {
assertThat(e).hasMessageThat().contains("the empty string is not a valid target");
}
}
@Test
public void testMistypedTargetKeepGoing() throws Exception {
LoadingResult result = tester.loadKeepGoing("foo//bar:missing");
// Legacy loading phase does _not_ report a target pattern error, and it's work to fix, so we
// skip this check for now.
if (useSkyframeTargetPatternEval()) {
assertThat(result.hasTargetPatternError()).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");
LoadingResult loadingResult = tester.loadTestsKeepGoing("//base:missing");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).isFalse();
assertThat(loadingResult.getTargets()).isEmpty();
assertThat(loadingResult.getTestsToRun()).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'])");
LoadingResult loadingResult = assertNoErrors(tester.load("//cc:all"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets());
// Explicitly specified on the command line.
loadingResult = assertNoErrors(tester.load("//cc:my_lib"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//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' })");
LoadingResult loadingResult = assertNoErrors(tester.load("//config:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//config:somelib"));
// Explicitly specified on the command line.
loadingResult = assertNoErrors(tester.load("//config:configa"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//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()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
@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()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
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();
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
@Test
public void testTestFilteringIncludingManual() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--build_manual_tests");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2", "//tests:t3"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
@Test
public void testTestFilteringBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--build_tests_only");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
@Test
public void testTestFilteringSize() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_size_filters=small");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t2"));
assertThat(loadingResult.getTestsToRun()).containsExactlyElementsIn(getTargets("//tests:t1"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets());
}
@Test
public void testTestFilteringSizeAndBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_size_filters=small", "--build_tests_only");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//tests:t1"));
assertThat(loadingResult.getTestsToRun()).containsExactlyElementsIn(getTargets("//tests:t1"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets("//tests:t2"));
}
@Test
public void testTestFilteringLocalAndBuildTestsOnly() throws Exception {
writeBuildFilesForTestFiltering();
tester.useLoadingOptions("--test_tag_filters=local", "--build_tests_only");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//tests:all", "//tests:t3"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t3"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//tests:t1", "//tests:t3"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets()).containsExactlyElementsIn(getTargets("//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'])");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//cc:tests"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//cc:my_test"));
assertThat(loadingResult.getTestsToRun()).containsExactlyElementsIn(getTargets("//cc:my_test"));
assertThat(tester.getOriginalTargets())
.containsExactlyElementsIn(getTargets("//cc:tests", "//cc:my_test"));
assertThat(tester.getTestSuiteTargets())
.containsExactlyElementsIn(getTargets("//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");
LoadingResult loadingResult = tester.loadTestsKeepGoing("//ts:tests");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).isFalse();
tester.assertContainsError("no such package 'nonexistent'");
}
@Test
public void testTestSuiteExpansionFailsForBuild() throws Exception {
tester.addFile("ts/BUILD",
"test_suite(name = 'tests', tests = [':nonexistent_test'])");
LoadingResult loadingResult = tester.loadKeepGoing("//ts:tests");
assertThat(loadingResult.hasTargetPatternError()).isFalse();
assertThat(loadingResult.hasLoadingError()).isTrue();
tester.assertContainsError(
"expecting a test or a test_suite rule but '//ts:nonexistent_test' is not one");
}
@Test
public void testTestSuiteExpansionFailsMissingTarget() throws Exception {
tester.addFile("other/BUILD", "");
tester.addFile("ts/BUILD",
"test_suite(name = 'tests', tests = ['//other:no_such_test'])");
LoadingResult loadingResult = tester.loadTestsKeepGoing("//ts:tests");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).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 = [])");
LoadingResult loadingResult = tester.loadTestsKeepGoing("//ts:all");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
assertThat(loadingResult.hasLoadingError()).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");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//foo:all"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//foo:foo", "//foo:baz"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//foo:foo", "//foo:baz"));
assertThat(tester.getFilteredTargets()).containsExactlyElementsIn(getTargets());
assertThat(tester.getTestFilteredTargets())
.containsExactlyElementsIn(getTargets("//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'])");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//cc:tests", "-//cc:my_test"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//cc:my_other_test", "//cc:my_test"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//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'])");
LoadingResult loadingResult = assertNoErrors(tester.loadTests("//cc:all_tests", "-//cc:tests"));
assertThat(loadingResult.getTargets())
.containsExactlyElementsIn(getTargets("//cc:my_other_test"));
assertThat(loadingResult.getTestsToRun())
.containsExactlyElementsIn(getTargets("//cc:my_other_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'])");
LoadingResult loadingResult = tester.loadTests("//cc:tests", "-//cc:my_test");
tester.assertContainsWarning("All specified test targets were excluded by filters");
assertThat(loadingResult.getTestsToRun()).containsExactlyElementsIn(getTargets());
}
@Test
public void testRepeatedSameLoad() throws Exception {
tester.addFile("base/BUILD",
"filegroup(name = 'hello', srcs = ['foo.txt'])");
LoadingResult firstResult = assertNoErrors(tester.load("//base:hello"));
LoadingResult secondResult = assertNoErrors(tester.load("//base:hello"));
assertThat(secondResult.getTargets()).isEqualTo(firstResult.getTargets());
assertThat(secondResult.getTestsToRun()).isEqualTo(firstResult.getTestsToRun());
}
/**
* 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");
Target result = Iterables.getOnlyElement(assertNoErrors(tester.load("//foo:x")).getTargets());
assertThat(
Iterables.transform(result.getAssociatedRule().getLabels(), Functions.toStringFunction()))
.containsExactly("//foo:a.y");
tester.addFile("foo/b.y");
tester.sync();
result = Iterables.getOnlyElement(assertNoErrors(tester.load("//foo:x")).getTargets());
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'])");
LoadingResult loadingResult = assertNoErrors(tester.load("//suite:a"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//suite:c"));
}
@Test
public void testTopLevelTargetErrorsPrintedExactlyOnce_NoKeepGoing() throws Exception {
tester.addFile("bad/BUILD",
"sh_binary(name = 'bad', srcs = ['bad.sh'])",
"undefined_symbol");
try {
tester.load("//bad");
fail();
} catch (TargetParsingException expected) {
}
tester.assertContainsEventWithFrequency("name 'undefined_symbol' is not defined", 1);
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//bad");
assertThat(err.getSkipped()).isFalse();
}
@Test
public void testTopLevelTargetErrorsPrintedExactlyOnce_KeepGoing() throws Exception {
tester.addFile("bad/BUILD",
"sh_binary(name = 'bad', srcs = ['bad.sh'])",
"undefined_symbol");
LoadingResult loadingResult = tester.loadKeepGoing("//bad");
if (!useSkyframeTargetPatternEval()) {
// The LegacyLoadingPhaseRunner drops the error on the floor. The target can be resolved
// after all, even if the package is in error.
assertThat(loadingResult.hasTargetPatternError()).isFalse();
} else {
assertThat(loadingResult.hasTargetPatternError()).isTrue();
}
tester.assertContainsEventWithFrequency("name 'undefined_symbol' is not defined", 1);
}
@Test
public void testCompileOneDependency() throws Exception {
tester.addFile("base/BUILD",
"cc_library(name = 'hello', srcs = ['hello.cc'])");
tester.useLoadingOptions("--compile_one_dependency");
LoadingResult loadingResult = assertNoErrors(tester.load("base/hello.cc"));
assertThat(loadingResult.getTargets()).containsExactlyElementsIn(getTargets("//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 {
LoadingResult loadingResult = tester.load("base/hello.cc");
assertThat(loadingResult.hasLoadingError()).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");
LoadingResult loadingResult = tester.loadKeepGoing("base/hello.cc");
assertThat(loadingResult.hasLoadingError()).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");
try {
tester.load("//base:hello");
fail();
} catch (TargetParsingException e) {
assertThat(e).hasMessageThat()
.contains("--compile_one_dependency target '//base:hello' must be a file");
}
}
@Test
public void testParsingFailureReported() throws Exception {
LoadingResult loadingResult = tester.loadKeepGoing("//does_not_exist");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
ParsingFailedEvent event = tester.findPostOnce(ParsingFailedEvent.class);
assertThat(event.getPattern()).isEqualTo("//does_not_exist");
assertThat(event.getMessage()).contains("BUILD file not found on package path");
}
@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')");
if (useSkyframeTargetPatternEval()) {
// 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 LoadingResult.
try {
tester.load("//test:cycle1");
fail();
} catch (TargetParsingException e) {
assertThat(e).hasMessageThat().contains("cycles detected");
}
} else {
LoadingResult loadingResult = tester.loadKeepGoing("//test:cycle1");
assertThat(loadingResult.hasTargetPatternError()).isTrue();
}
tester.assertContainsEventWithFrequency("cycle detected in extension", 1);
PatternExpandingError err = tester.findPostOnce(PatternExpandingError.class);
assertThat(err.getPattern()).containsExactly("//test:cycle1");
assertThat(err.getSkipped()).isEqualTo(!useSkyframeTargetPatternEval());
}
@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')");
try {
tester.load("//test:cycle1");
fail();
} catch (TargetParsingException e) {
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");
assertThat(err.getSkipped()).isFalse();
}
private void assertCircularSymlinksDuringTargetParsing(String targetPattern) throws Exception {
try {
tester.load(targetPattern);
fail();
} catch (TargetParsingException e) {
// Expected.
tester.assertContainsError("circular symlinks detected");
}
}
private LoadingResult assertNoErrors(LoadingResult loadingResult) {
assertThat(loadingResult.hasTargetPatternError()).isFalse();
assertThat(loadingResult.hasLoadingError()).isFalse();
tester.assertNoEvents();
return loadingResult;
}
private static class LoadingPhaseTester {
private final ManualClock clock = new ManualClock();
private final Path workspace;
private final AnalysisMock analysisMock;
private final SkyframeExecutor skyframeExecutor;
private final List<Path> changes = new ArrayList<>();
private final LoadingPhaseRunner loadingPhaseRunner;
private final BlazeDirectories directories;
private final ActionKeyContext actionKeyContext = new ActionKeyContext();
private LoadingOptions options;
private final StoredEventHandler storedErrors;
private LoadingCallback loadingCallback;
private TargetParsingCompleteEvent targetParsingCompleteEvent;
private LoadingPhaseCompleteEvent loadingPhaseCompleteEvent;
private MockToolsConfig mockToolsConfig;
public LoadingPhaseTester(boolean useNewImpl) throws IOException {
FileSystem fs = new InMemoryFileSystem(clock);
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());
FileSystemUtils.deleteTree(workspace.getRelative("base"));
ConfiguredRuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider();
PackageFactory pkgFactory =
analysisMock.getPackageFactoryBuilderForTesting(directories).build(ruleClassProvider);
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 =
SequencedSkyframeExecutor.create(
pkgFactory,
fs,
directories,
actionKeyContext,
null, /* workspaceStatusActionFactory -- not used */
ruleClassProvider.getBuildInfoFactories(),
ImmutableList.<DiffAwareness.Factory>of(),
analysisMock.getSkyFunctions(directories),
ImmutableList.<SkyValueDirtinessChecker>of(),
BazelSkyframeExecutorConstants.HARDCODED_BLACKLISTED_PACKAGE_PREFIXES,
BazelSkyframeExecutorConstants.ADDITIONAL_BLACKLISTED_PACKAGE_PREFIXES_FILE,
BazelSkyframeExecutorConstants.CROSS_REPOSITORY_LABEL_VIOLATION_STRATEGY,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY,
BazelSkyframeExecutorConstants.ACTION_ON_IO_EXCEPTION_READING_BUILD_FILE,
defaultBuildOptions);
TestConstants.processSkyframeExecutorForTesting(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.preparePackageLoading(
pkgLocator,
packageCacheOptions,
Options.getDefaults(SkylarkSemanticsOptions.class),
analysisMock.getDefaultsPackageContent(),
UUID.randomUUID(),
ImmutableMap.<String, String>of(),
ImmutableMap.<String, String>of(),
new TimestampGranularityMonitor(clock));
loadingPhaseRunner =
skyframeExecutor.getLoadingPhaseRunner(pkgFactory.getRuleClassNames(), useNewImpl);
this.options = Options.getDefaults(LoadingOptions.class);
}
public void setCallback(LoadingCallback loadingCallback) {
this.loadingCallback = loadingCallback;
}
public void useLoadingOptions(String... options) throws OptionsParsingException {
OptionsParser parser = OptionsParser.newOptionsParser(LoadingOptions.class);
parser.parse(ImmutableList.copyOf(options));
this.options = parser.getOptions(LoadingOptions.class);
}
public LoadingResult load(String... patterns) throws Exception {
return load(/*keepGoing=*/false, /*determineTests=*/false, patterns);
}
public LoadingResult loadKeepGoing(String... patterns) throws Exception {
return load(/*keepGoing=*/true, /*determineTests=*/false, patterns);
}
public LoadingResult loadTests(String... patterns) throws Exception {
return load(/*keepGoing=*/false, /*determineTests=*/true, patterns);
}
public LoadingResult loadTestsKeepGoing(String... patterns) throws Exception {
return load(/*keepGoing=*/true, /*determineTests=*/true, patterns);
}
public LoadingResult load(boolean keepGoing, boolean determineTests, String... patterns)
throws Exception {
sync();
storedErrors.clear();
LoadingResult result;
try {
result =
loadingPhaseRunner.execute(
storedErrors,
ImmutableList.copyOf(patterns),
PathFragment.EMPTY_FRAGMENT,
options,
keepGoing,
determineTests,
loadingCallback);
this.targetParsingCompleteEvent = findPost(TargetParsingCompleteEvent.class);
this.loadingPhaseCompleteEvent = findPost(LoadingPhaseCompleteEvent.class);
} catch (LoadingFailedException e) {
System.err.println(storedErrors.getEvents());
throw e;
}
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();
}
FileSystemUtils.createDirectoryAndParents(buildFile.getParentDirectory());
FileSystemUtils.writeContentAsLatin1(buildFile, Joiner.on('\n').join(content));
}
private void sync() throws InterruptedException {
String pkgContents = analysisMock.getDefaultsPackageContent();
skyframeExecutor.setupDefaultPackage(pkgContents);
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 List<Target> getTargets(String... targetNames) throws Exception {
List<Target> result = new ArrayList<>();
for (String targetName : targetNames) {
result.add(getTarget(targetName));
}
return result;
}
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<Target> getFilteredTargets() {
return targetParsingCompleteEvent.getFilteredTargets();
}
public ImmutableSet<Target> getTestFilteredTargets() {
return targetParsingCompleteEvent.getTestFilteredTargets();
}
public ImmutableSet<Target> getOriginalTargets() {
return targetParsingCompleteEvent.getTargets();
}
public ImmutableSet<Target> getTestSuiteTargets() {
return loadingPhaseCompleteEvent.getFilteredTargets();
}
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());
}
}
}