blob: c6c6e0a16404c8bf626c0cd7a9cae2d645cabc6a [file] [log] [blame]
// Copyright 2017 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.skyframe.packages;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.server.FailureDetails.StarlarkLoading.Code.COMPILE_ERROR;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertContainsEvent;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertDoesNotContainEvents;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertNoEvents;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.skyframe.BzlLoadFailedException;
import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
import com.google.devtools.build.lib.skyframe.packages.PackageLoader.LoadingContext;
import com.google.devtools.build.lib.skyframe.packages.PackageLoader.StarlarkModuleLoadingException;
import com.google.devtools.build.lib.util.ValueOrException;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
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 java.util.concurrent.ForkJoinPool;
import net.starlark.java.eval.Module;
import org.junit.Before;
import org.junit.Test;
/** Abstract base class of a unit test for a {@link AbstractPackageLoader} implementation. */
public abstract class AbstractPackageLoaderTest {
protected Path workspaceDir;
protected StoredEventHandler handler;
protected FileSystem fs;
protected Root root;
private Reporter reporter;
@Before
public final void init() throws Exception {
fs = new InMemoryFileSystem(DigestHashFunction.SHA256);
workspaceDir = fs.getPath("/workspace/");
workspaceDir.createDirectoryAndParents();
root = Root.fromPath(workspaceDir);
reporter = new Reporter(new EventBus());
handler = new StoredEventHandler();
reporter.addHandler(handler);
}
protected abstract AbstractPackageLoader.Builder newPackageLoaderBuilder(Root workspaceDir);
protected AbstractPackageLoader.Builder newPackageLoaderBuilder() {
return newPackageLoaderBuilder(root).useDefaultStarlarkSemantics().setCommonReporter(reporter);
}
protected abstract ForkJoinPool extractLegacyGlobbingForkJoinPool(PackageLoader packageLoader);
protected PackageLoader newPackageLoader() {
return newPackageLoaderBuilder().build();
}
@Test
public void simpleNoPackage() {
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("nope"));
NoSuchPackageException expected;
try (PackageLoader pkgLoader = newPackageLoader()) {
expected = assertThrows(NoSuchPackageException.class, () -> pkgLoader.loadPackage(pkgId));
}
assertThat(expected)
.hasMessageThat()
.startsWith("no such package 'nope': BUILD file not found");
assertNoEvents(handler.getEvents());
}
@Test
public void simpleBadPackage() throws Exception {
file("bad/BUILD", "invalidBUILDsyntax");
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("bad"));
Package badPkg;
try (PackageLoader pkgLoader = newPackageLoader()) {
badPkg = pkgLoader.loadPackage(pkgId);
}
assertThat(badPkg.containsErrors()).isTrue();
assertContainsEvent(handler.getEvents(), "invalidBUILDsyntax");
}
@Test
public void simpleGoodPackage() throws Exception {
file("good/BUILD", "sh_library(name = 'good')");
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("good"));
Package goodPkg;
try (PackageLoader pkgLoader = newPackageLoader()) {
goodPkg = pkgLoader.loadPackage(pkgId);
}
assertThat(goodPkg.containsErrors()).isFalse();
assertThat(goodPkg.getTarget("good").getAssociatedRule().getRuleClass())
.isEqualTo("sh_library");
assertNoEvents(handler.getEvents());
}
@Test
public void simpleMultipleGoodPackage() throws Exception {
file("good1/BUILD", "sh_library(name = 'good1')");
file("good2/BUILD", "sh_library(name = 'good2')");
PackageIdentifier pkgId1 = PackageIdentifier.createInMainRepo(PathFragment.create("good1"));
PackageIdentifier pkgId2 = PackageIdentifier.createInMainRepo(PathFragment.create("good2"));
ImmutableMap<PackageIdentifier, ValueOrException<Package, NoSuchPackageException>> pkgs;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<PackageIdentifier, Package, NoSuchPackageException> result =
pkgLoader.makeLoadingContext().loadPackages(ImmutableList.of(pkgId1, pkgId2));
pkgs = result.getLoadedValues();
events = result.getEvents();
}
assertThat(pkgs.get(pkgId1).get().containsErrors()).isFalse();
assertThat(pkgs.get(pkgId2).get().containsErrors()).isFalse();
assertThat(pkgs.get(pkgId1).get().getTarget("good1").getAssociatedRule().getRuleClass())
.isEqualTo("sh_library");
assertThat(pkgs.get(pkgId2).get().getTarget("good2").getAssociatedRule().getRuleClass())
.isEqualTo("sh_library");
assertNoEvents(events);
assertNoEvents(handler.getEvents());
}
@Test
public void testGoodAndBadAndMissingPackages() throws Exception {
file("bad/BUILD", "invalidBUILDsyntax");
PackageIdentifier badPkgId = PackageIdentifier.createInMainRepo(PathFragment.create("bad"));
file("good/BUILD", "sh_library(name = 'good')");
PackageIdentifier goodPkgId = PackageIdentifier.createInMainRepo(PathFragment.create("good"));
PackageIdentifier missingPkgId = PackageIdentifier.createInMainRepo("missing");
ImmutableMap<PackageIdentifier, ValueOrException<Package, NoSuchPackageException>> pkgs;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<PackageIdentifier, Package, NoSuchPackageException> result =
pkgLoader
.makeLoadingContext()
.loadPackages(ImmutableList.of(badPkgId, goodPkgId, missingPkgId));
pkgs = result.getLoadedValues();
events = result.getEvents();
}
Package goodPkg = pkgs.get(goodPkgId).get();
assertThat(goodPkg.containsErrors()).isFalse();
Package badPkg = pkgs.get(badPkgId).get();
assertThat(badPkg.containsErrors()).isTrue();
assertThrows(NoSuchPackageException.class, () -> pkgs.get(missingPkgId).get());
assertContainsEvent(events, "invalidBUILDsyntax");
assertContainsEvent(handler.getEvents(), "invalidBUILDsyntax");
}
@Test
public void loadPackagesToleratesDuplicates() throws Exception {
file("good1/BUILD", "sh_library(name = 'good1')");
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("good1"));
ImmutableMap<PackageIdentifier, ValueOrException<Package, NoSuchPackageException>> pkgs;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<PackageIdentifier, Package, NoSuchPackageException> result =
pkgLoader.makeLoadingContext().loadPackages(ImmutableList.of(pkgId, pkgId));
pkgs = result.getLoadedValues();
events = result.getEvents();
}
assertThat(pkgs.get(pkgId).get().containsErrors()).isFalse();
assertThat(pkgs.get(pkgId).get().getTarget("good1").getAssociatedRule().getRuleClass())
.isEqualTo("sh_library");
assertNoEvents(events);
assertNoEvents(handler.getEvents());
}
@Test
public void simpleGoodPackage_Starlark() throws Exception {
file(
"good/good.bzl",
"""
def f(x):
native.sh_library(name = x)
""");
file(
"good/BUILD",
"""
load("//good:good.bzl", "f")
f("good")
""");
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("good"));
Package goodPkg;
try (PackageLoader pkgLoader = newPackageLoader()) {
goodPkg = pkgLoader.loadPackage(pkgId);
}
assertThat(goodPkg.containsErrors()).isFalse();
assertThat(goodPkg.getTarget("good").getAssociatedRule().getRuleClass())
.isEqualTo("sh_library");
assertNoEvents(handler.getEvents());
}
@Test
public void externalFile_SupportedByDefault() throws Exception {
Path externalPath = file(absolutePath("/external/BUILD"), "sh_library(name = 'foo')");
symlink("foo/BUILD", externalPath);
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("foo"));
Package fooPkg;
try (PackageLoader pkgLoader = newPackageLoader()) {
fooPkg = pkgLoader.loadPackage(pkgId);
}
assertThat(fooPkg.containsErrors()).isFalse();
assertThat(fooPkg.getTarget("foo").getTargetKind()).isEqualTo("sh_library rule");
assertNoEvents(handler.getEvents());
}
@Test
public void externalFile_AssumeNonExistentAndImmutable() throws Exception {
Path externalPath = file(absolutePath("/external/BUILD"), "sh_library(name = 'foo')");
symlink("foo/BUILD", externalPath);
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo(PathFragment.create("foo"));
NoSuchPackageException expected;
try (PackageLoader pkgLoader =
newPackageLoaderBuilder()
.setExternalFileAction(
ExternalFileAction.ASSUME_NON_EXISTENT_AND_IMMUTABLE_FOR_EXTERNAL_PATHS)
.build()) {
expected = assertThrows(NoSuchPackageException.class, () -> pkgLoader.loadPackage(pkgId));
}
assertThat(expected).hasMessageThat().contains("no such package 'foo': BUILD file not found");
}
@Test
public void testNonPackageEventsReported() throws Exception {
path("foo").createDirectoryAndParents();
symlink("foo/infinitesymlinkpkg", path("foo/infinitesymlinkpkg/subdir"));
PackageIdentifier pkgId = PackageIdentifier.createInMainRepo("foo/infinitesymlinkpkg");
ImmutableMap<PackageIdentifier, ValueOrException<Package, NoSuchPackageException>> pkgs;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<PackageIdentifier, Package, NoSuchPackageException> result =
pkgLoader.makeLoadingContext().loadPackages(ImmutableList.of(pkgId));
pkgs = result.getLoadedValues();
events = result.getEvents();
}
assertThrows(NoSuchPackageException.class, () -> pkgs.get(pkgId).get());
assertContainsEvent(events, "infinite symlink expansion detected");
}
@Test
public void testClosesForkJoinPool() throws Exception {
PackageLoader pkgLoader = newPackageLoader();
ForkJoinPool forkJoinPool = extractLegacyGlobbingForkJoinPool(pkgLoader);
assertThat(forkJoinPool.isShutdown()).isFalse();
pkgLoader.close();
assertThat(forkJoinPool.isShutdown()).isTrue();
}
@Test
public void loadingContext_loadModules_basicFunctionality() throws Exception {
file("x/BUILD");
file(
"x/foo.bzl",
"""
'''Module foo'''
load('//y:bar.bzl', 'bar')
def foo(): bar()
""");
file("y/BUILD");
file(
"y/bar.bzl",
"""
'''Module bar'''
def bar(): pass
""");
Label fooLabel = Label.parseCanonicalUnchecked("//x:foo.bzl");
Label barLabel = Label.parseCanonicalUnchecked("//y:bar.bzl");
ImmutableMap<Label, ValueOrException<Module, StarlarkModuleLoadingException>> modules;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<Label, Module, StarlarkModuleLoadingException> result =
pkgLoader.makeLoadingContext().loadModules(ImmutableList.of(fooLabel, barLabel));
modules = result.getLoadedValues();
events = result.getEvents();
}
assertThat(modules.keySet()).containsExactly(fooLabel, barLabel);
assertThat(modules.get(fooLabel).isPresent()).isTrue();
assertThat(modules.get(fooLabel).get().getDocumentation()).isEqualTo("Module foo");
assertThat(modules.get(fooLabel).get()).isSameInstanceAs(modules.get(fooLabel).getUnchecked());
assertThat(modules.get(barLabel).isPresent()).isTrue();
assertThat(modules.get(barLabel).get().getDocumentation()).isEqualTo("Module bar");
assertThat(modules.get(barLabel).get()).isSameInstanceAs(modules.get(barLabel).getUnchecked());
assertNoEvents(events);
}
@Test
public void loadingContext_loadModules_failsOnBrokenModule() throws Exception {
file("x/BUILD");
file("x/foo.bzl", "syntax error");
Label fooLabel = Label.parseCanonicalUnchecked("//x:foo.bzl");
ImmutableMap<Label, ValueOrException<Module, StarlarkModuleLoadingException>> modules;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<Label, Module, StarlarkModuleLoadingException> result =
pkgLoader.makeLoadingContext().loadModules(ImmutableList.of(fooLabel));
modules = result.getLoadedValues();
events = result.getEvents();
}
assertThat(modules.keySet()).containsExactly(fooLabel);
ValueOrException<Module, StarlarkModuleLoadingException> valueOrException =
modules.get(fooLabel);
assertThat(valueOrException.isPresent()).isFalse();
StarlarkModuleLoadingException exception =
assertThrows(StarlarkModuleLoadingException.class, valueOrException::get);
assertThat(exception).hasMessageThat().contains("compilation of module 'x/foo.bzl' failed");
assertThat(exception).hasCauseThat().isInstanceOf(BzlLoadFailedException.class);
assertThat(exception.getFailureDetail().get().getStarlarkLoading().getCode())
.isEqualTo(COMPILE_ERROR);
IllegalStateException uncheckedException =
assertThrows(IllegalStateException.class, valueOrException::getUnchecked);
assertThat(uncheckedException).hasCauseThat().isEqualTo(exception);
assertThat(handler.getEvents()).containsExactlyElementsIn(events);
assertContainsEvent(events, "syntax error");
}
@Test
public void loadingContext_loadModules_failsOnCycle() throws Exception {
file("x/BUILD");
file(
"x/foo.bzl",
"""
load("//y:bar.bzl", "bar")
def foo(): return bar
""");
file("y/BUILD");
file(
"y/bar.bzl",
"""
load("//x:foo.bzl", "foo")
def bar(): return foo
""");
Label fooLabel = Label.parseCanonicalUnchecked("//x:foo.bzl");
ImmutableMap<Label, ValueOrException<Module, StarlarkModuleLoadingException>> modules;
ImmutableList<Event> events;
try (PackageLoader pkgLoader = newPackageLoader()) {
PackageLoader.Result<Label, Module, StarlarkModuleLoadingException> result =
pkgLoader.makeLoadingContext().loadModules(ImmutableList.of(fooLabel));
modules = result.getLoadedValues();
events = result.getEvents();
}
assertThat(modules.keySet()).containsExactly(fooLabel);
ValueOrException<Module, StarlarkModuleLoadingException> valueOrException =
modules.get(fooLabel);
assertThat(valueOrException.isPresent()).isFalse();
StarlarkModuleLoadingException exception =
assertThrows(StarlarkModuleLoadingException.class, valueOrException::get);
assertThat(exception).hasMessageThat().contains("Cycle encountered while loading //x:foo.bzl");
assertThat(exception).hasCauseThat().isNull();
assertThat(exception.getFailureDetail())
.isEmpty(); // TODO(b/331221948): we ought to define a failure detail for load() cycles
IllegalStateException uncheckedException =
assertThrows(IllegalStateException.class, valueOrException::getUnchecked);
assertThat(uncheckedException).hasCauseThat().isEqualTo(exception);
assertThat(events).isEmpty();
}
@Test
public void loadingContext_resetsLoadedEvents() throws Exception {
file("x/BUILD", "invalidSyntax_pkg_x");
file("x/foo.bzl", "invalidSyntax_foo_bzl");
file("y/BUILD", "invalidSyntax_pkg_y");
file("y/bar.bzl", "invalidSyntax_bar_bzl");
Label fooLabel = Label.parseCanonicalUnchecked("//x:foo.bzl");
Label barLabel = Label.parseCanonicalUnchecked("//y:bar.bzl");
ImmutableList<Event> eventsAfterLoadingFooBzl;
ImmutableList<Event> eventsAfterLoadingPkgX;
ImmutableList<Event> eventsAfterLoadingBarBzl;
ImmutableList<Event> eventsAfterLoadingPkgY;
try (PackageLoader pkgLoader = newPackageLoader()) {
LoadingContext loadingContext = pkgLoader.makeLoadingContext();
eventsAfterLoadingFooBzl = loadingContext.loadModules(ImmutableList.of(fooLabel)).getEvents();
eventsAfterLoadingPkgX =
loadingContext
.loadPackages(
ImmutableList.of(PackageIdentifier.createInMainRepo(PathFragment.create("x"))))
.getEvents();
eventsAfterLoadingPkgY =
loadingContext
.loadPackages(
ImmutableList.of(PackageIdentifier.createInMainRepo(PathFragment.create("y"))))
.getEvents();
eventsAfterLoadingBarBzl = loadingContext.loadModules(ImmutableList.of(barLabel)).getEvents();
}
assertContainsEvent(eventsAfterLoadingFooBzl, "invalidSyntax_foo_bzl");
assertDoesNotContainEvents(
eventsAfterLoadingFooBzl,
"invalidSyntax_pkg_x",
"invalidSyntax_pkg_y",
"invalidSyntax_bar_bzl");
assertContainsEvent(eventsAfterLoadingPkgX, "invalidSyntax_pkg_x");
assertDoesNotContainEvents(
eventsAfterLoadingPkgX,
"invalidSyntax_pkg_y",
"invalidSyntax_foo_bzl",
"invalidSyntax_bar_bzl");
assertContainsEvent(eventsAfterLoadingPkgY, "invalidSyntax_pkg_y");
assertDoesNotContainEvents(
eventsAfterLoadingPkgY,
"invalidSyntax_pkg_x",
"invalidSyntax_foo_bzl",
"invalidSyntax_bar_bzl");
assertContainsEvent(eventsAfterLoadingBarBzl, "invalidSyntax_bar_bzl");
assertDoesNotContainEvents(
eventsAfterLoadingBarBzl,
"invalidSyntax_pkg_x",
"invalidSyntax_pkg_y",
"invalidSyntax_foo_bzl");
}
@Test
public void loadingContext_getRepositoryMapping_basicFunctionality() throws Exception {
RepositoryMapping repositoryMapping;
try (PackageLoader pkgLoader = newPackageLoader()) {
LoadingContext loadingContext = pkgLoader.makeLoadingContext();
repositoryMapping = loadingContext.getRepositoryMapping();
}
assertThat(repositoryMapping.get("")).isEqualTo(RepositoryName.MAIN);
assertNoEvents(handler.getEvents());
}
protected Path path(String rootRelativePath) {
return workspaceDir.getRelative(PathFragment.create(rootRelativePath));
}
protected Path absolutePath(String absolutePath) {
return fs.getPath(absolutePath);
}
protected Path file(String fileName, String... contents) throws Exception {
return file(path(fileName), contents);
}
protected Path file(Path path, String... contents) throws Exception {
path.getParentDirectory().createDirectoryAndParents();
FileSystemUtils.writeContentAsLatin1(path, Joiner.on("\n").join(contents));
return path;
}
protected Path symlink(String linkPathString, Path linkTargetPath) throws Exception {
Path path = path(linkPathString);
FileSystemUtils.ensureSymbolicLink(path, linkTargetPath);
return path;
}
}