| // 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.skyframe; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.base.Ascii; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.packages.NoSuchTargetException; |
| import com.google.devtools.build.lib.packages.Package; |
| import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.WorkspaceFileValue; |
| import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; |
| import com.google.devtools.build.lib.util.AbruptExitException; |
| 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.skyframe.EvaluationResult; |
| import com.google.devtools.build.skyframe.SkyKey; |
| import com.google.devtools.build.skyframe.SkyValue; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| import net.starlark.java.syntax.ParserInput; |
| import net.starlark.java.syntax.StarlarkFile; |
| import net.starlark.java.syntax.Statement; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Test for {@link WorkspaceFileFunction}. |
| */ |
| @RunWith(JUnit4.class) |
| public class WorkspaceFileFunctionTest extends BuildViewTestCase { |
| @Override |
| protected Iterable<EnvironmentExtension> getEnvironmentExtensions() { |
| return ImmutableList.of(); |
| } |
| |
| private static Label getLabelMapping(Package pkg, String name) throws NoSuchTargetException { |
| return (Label) ((Rule) pkg.getTarget(name)).getAttr("actual"); |
| } |
| |
| private RootedPath createWorkspaceFile(String... contents) throws IOException { |
| Path workspacePath = scratch.overwriteFile("WORKSPACE", contents); |
| return RootedPath.toRootedPath( |
| Root.fromPath(workspacePath.getParentDirectory()), |
| PathFragment.create(workspacePath.getBaseName())); |
| } |
| |
| private <T extends SkyValue> EvaluationResult<T> eval(SkyKey key) |
| throws InterruptedException, AbruptExitException { |
| getSkyframeExecutor() |
| .invalidateFilesUnderPathForTesting( |
| reporter, |
| ModifiedFileSet.builder().modify(PathFragment.create("WORKSPACE")).build(), |
| Root.fromPath(rootDirectory)); |
| return SkyframeExecutorTestUtils.evaluate( |
| getSkyframeExecutor(), key, /*keepGoing=*/ false, reporter); |
| } |
| |
| @Test |
| public void testLoadToChunkMapSimple() throws Exception { |
| scratch.file("a.bzl", "a = 'a'"); |
| scratch.file("b.bzl", "b = 'b'"); |
| scratch.file("BUILD", ""); |
| RootedPath workspace = |
| createWorkspaceFile( |
| "workspace(name = 'good')", |
| "load('//:a.bzl', 'a')", |
| "x = 1 #for chunk break", |
| "load('//:b.bzl', 'b')"); |
| SkyKey key1 = WorkspaceFileValue.key(workspace, 1); |
| EvaluationResult<WorkspaceFileValue> result1 = eval(key1); |
| WorkspaceFileValue value1 = result1.get(key1); |
| assertThat(value1.getLoadToChunkMap()).containsEntry("//:a.bzl", 1); |
| |
| SkyKey key2 = WorkspaceFileValue.key(workspace, 2); |
| EvaluationResult<WorkspaceFileValue> result2 = eval(key2); |
| WorkspaceFileValue value2 = result2.get(key2); |
| assertThat(value2.getLoadToChunkMap()).containsEntry("//:a.bzl", 1); |
| assertThat(value2.getLoadToChunkMap()).containsEntry("//:b.bzl", 2); |
| } |
| |
| @Test |
| public void testLoadToChunkMapDoesNotOverrideDuplicate() throws Exception { |
| scratch.file("a.bzl", "a = 'a'"); |
| scratch.file("BUILD", ""); |
| RootedPath workspace = |
| createWorkspaceFile( |
| "workspace(name = 'good')", |
| "load('//:a.bzl', 'a')", |
| "x = 1 #for chunk break", |
| "load('//:a.bzl', 'a')"); |
| SkyKey key1 = WorkspaceFileValue.key(workspace, 1); |
| EvaluationResult<WorkspaceFileValue> result1 = eval(key1); |
| WorkspaceFileValue value1 = result1.get(key1); |
| assertThat(value1.getLoadToChunkMap()).containsEntry("//:a.bzl", 1); |
| |
| SkyKey key2 = WorkspaceFileValue.key(workspace, 2); |
| EvaluationResult<WorkspaceFileValue> result2 = eval(key2); |
| WorkspaceFileValue value2 = result2.get(key2); |
| assertThat(value2.getLoadToChunkMap()).containsEntry("//:a.bzl", 1); |
| assertThat(value2.getLoadToChunkMap()).doesNotContainEntry("//:a.bzl", 2); |
| } |
| |
| @Test |
| public void testRepositoryMappingInChunks() throws Exception { |
| scratch.file("b.bzl", "b = 'b'"); |
| scratch.file("BUILD", ""); |
| RootedPath workspace = |
| createWorkspaceFile( |
| "workspace(name = 'good')", |
| "local_repository(name = 'a', path = '../a', repo_mapping = {'@x' : '@y'})", |
| "load('//:b.bzl', 'b')", |
| "local_repository(name = 'b', path = '../b', repo_mapping = {'@x' : '@y'})"); |
| RepositoryName a = RepositoryName.create("a"); |
| RepositoryName b = RepositoryName.create("b"); |
| RepositoryName y = RepositoryName.create("y"); |
| RepositoryName main = RepositoryName.create(""); |
| |
| SkyKey key0 = WorkspaceFileValue.key(workspace, 0); |
| EvaluationResult<WorkspaceFileValue> result0 = eval(key0); |
| WorkspaceFileValue value0 = result0.get(key0); |
| assertThat(value0.getRepositoryMapping()) |
| .containsEntry(a, ImmutableMap.of("x", y, "good", main)); |
| |
| SkyKey key1 = WorkspaceFileValue.key(workspace, 1); |
| EvaluationResult<WorkspaceFileValue> result1 = eval(key1); |
| WorkspaceFileValue value1 = result1.get(key1); |
| assertThat(value1.getRepositoryMapping()) |
| .containsEntry(a, ImmutableMap.of("x", y, "good", main)); |
| assertThat(value1.getRepositoryMapping()) |
| .containsEntry(b, ImmutableMap.of("x", y, "good", main)); |
| } |
| |
| @Test |
| public void testBzlVisibility() throws Exception { |
| setBuildLanguageOptions("--experimental_bzl_visibility=true"); |
| |
| createWorkspaceFile( |
| "workspace(name = 'foo')", // |
| "load('//pkg:a.bzl', 'a')"); |
| scratch.file("pkg/BUILD"); |
| scratch.file( |
| "pkg/a.bzl", // |
| "visibility('private')"); |
| |
| reporter.removeHandler(failFastHandler); |
| SkyKey key = ExternalPackageFunction.key(); |
| eval(key); |
| // The evaluation result ends up being null, probably due to the test framework swallowing |
| // exceptions (similar to b/26382502). So let's just look for the error event instead of |
| // asserting on the exception. |
| assertContainsEvent( |
| "Starlark file //pkg:a.bzl is not visible for loading from package //. Check the file's" |
| + " `visibility()` declaration."); |
| } |
| |
| @Test |
| public void testInvalidRepo() throws Exception { |
| // Test intentionally introduces errors. |
| reporter.removeHandler(failFastHandler); |
| |
| createWorkspaceFile("workspace(name = 'foo$')"); |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(pkg.containsErrors()).isTrue(); |
| assertContainsEvent("Error in workspace: invalid user-provided repo name 'foo$'"); |
| } |
| |
| @Test |
| public void testBindFunction() throws Exception { |
| String[] lines = {"bind(name = 'foo/bar',", "actual = '//foo:bar')"}; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(getLabelMapping(pkg, "foo/bar")).isEqualTo(Label.parseCanonical("//foo:bar")); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testBindArgsReversed() throws Exception { |
| String[] lines = {"bind(actual = '//foo:bar', name = 'foo/bar')"}; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(getLabelMapping(pkg, "foo/bar")).isEqualTo(Label.parseCanonical("//foo:bar")); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testNonExternalBinding() throws Exception { |
| // Test intentionally introduces errors. |
| reporter.removeHandler(failFastHandler); |
| |
| // name must be a valid label name. |
| String[] lines = {"bind(name = 'foo:bar', actual = '//bar/baz')"}; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(pkg.containsErrors()).isTrue(); |
| assertContainsEvent("target names may not contain ':'"); |
| } |
| |
| @Test |
| public void testWorkspaceFileParsingError() throws Exception { |
| // Test intentionally introduces errors. |
| reporter.removeHandler(failFastHandler); |
| |
| // //external:bar:baz is not a legal package. |
| String[] lines = {"bind(name = 'foo/bar', actual = '//external:bar:baz')"}; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(pkg.containsErrors()).isTrue(); |
| assertContainsEvent("target names may not contain ':'"); |
| } |
| |
| @Test |
| public void testRegisterToolchainsInvalidPattern() throws Exception { |
| // Test intentionally introduces errors. |
| reporter.removeHandler(failFastHandler); |
| |
| // //external:bar:baz is not a legal package. |
| String[] lines = {"register_toolchains('/:invalid:label:syntax')"}; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(pkg.containsErrors()).isTrue(); |
| assertContainsEvent("not a valid absolute pattern"); |
| } |
| |
| @Test |
| public void testNoWorkspaceFile() throws Exception { |
| // Create and immediately delete to make sure we got the right file. |
| RootedPath workspacePath = createWorkspaceFile(); |
| workspacePath.asPath().delete(); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(pkg.containsErrors()).isFalse(); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testListBindFunction() throws Exception { |
| String[] lines = { |
| "L = ['foo', 'bar']", "bind(name = '%s/%s' % (L[0], L[1]),", "actual = '//foo:bar')" |
| }; |
| createWorkspaceFile(lines); |
| |
| SkyKey key = ExternalPackageFunction.key(); |
| EvaluationResult<PackageValue> evaluationResult = eval(key); |
| Package pkg = evaluationResult.get(key).getPackage(); |
| assertThat(getLabelMapping(pkg, "foo/bar")).isEqualTo(Label.parseCanonical("//foo:bar")); |
| assertNoEvents(); |
| } |
| |
| @Test |
| public void testMangledExternalWorkspaceFileIsIgnored() throws Exception { |
| scratch.file("secondary/WORKSPACE", "garbage"); |
| RootedPath workspace = |
| createWorkspaceFile( |
| "workspace(name = 'good')", |
| "local_repository(name = \"secondary\", path = \"./secondary/\")"); |
| |
| SkyKey key1 = WorkspaceFileValue.key(workspace, 1); |
| EvaluationResult<WorkspaceFileValue> result1 = eval(key1); |
| WorkspaceFileValue value1 = result1.get(key1); |
| RepositoryName main = RepositoryName.create(""); |
| RepositoryName secondary = RepositoryName.create("secondary"); |
| assertThat(value1.getRepositoryMapping()) |
| .containsEntry(secondary, ImmutableMap.of("good", main)); |
| assertNoEvents(); |
| } |
| // tests of splitChunks, an internal helper function |
| |
| @Test |
| public void testChunksNoLoad() { |
| assertThat(split(parse("foo_bar = 1"))).isEqualTo("[(assignment)]"); |
| } |
| |
| @Test |
| public void testChunksOneLoadAtTop() { |
| assertThat( |
| split( |
| parse( |
| "load('//:foo.bzl', 'bar')", // |
| "foo_bar = 1"))) |
| .isEqualTo("[(load assignment)]"); |
| } |
| |
| @Test |
| public void testChunksOneLoad() { |
| assertThat( |
| split( |
| parse( |
| "foo_bar = 1", |
| // |
| "load('//:foo.bzl', 'bar')"))) |
| .isEqualTo("[(assignment)][(load)]"); |
| } |
| |
| @Test |
| public void testChunksTwoSuccessiveLoads() { |
| assertThat( |
| split( |
| parse( |
| "foo_bar = 1", |
| // |
| "load('//:foo.bzl', 'bar')", |
| "load('//:bar.bzl', 'foo')"))) |
| .isEqualTo("[(assignment)][(load load)]"); |
| } |
| |
| @Test |
| public void testChunksTwoSucessiveLoadsWithNonLoadStatement() { |
| assertThat( |
| split( |
| parse( |
| "foo_bar = 1", |
| // |
| "load('//:foo.bzl', 'bar')", |
| "load('//:bar.bzl', 'foo')", |
| "local_repository(name = 'foobar', path = '/bar/foo')"))) |
| .isEqualTo("[(assignment)][(load load expression)]"); |
| } |
| |
| @Test |
| public void testChunksThreeLoadsThreeSegments() { |
| assertThat( |
| split( |
| parse( |
| "foo_bar = 1", |
| // |
| "load('//:foo.bzl', 'bar')", |
| "load('//:bar.bzl', 'foo')", |
| "local_repository(name = 'foobar', path = '/bar/foo')", |
| // |
| "load('@foobar//:baz.bzl', 'bleh')"))) |
| .isEqualTo("[(assignment)][(load load expression)][(load)]"); |
| } |
| |
| @Test |
| public void testChunksThreeLoadsThreeSegmentsWithContent() { |
| assertThat( |
| split( |
| parse( |
| "foo_bar = 1", |
| // |
| "load('//:foo.bzl', 'bar')", |
| "load('//:bar.bzl', 'foo')", |
| "local_repository(name = 'foobar', path = '/bar/foo')", |
| // |
| "load('@foobar//:baz.bzl', 'bleh')", |
| "bleh()"))) |
| .isEqualTo("[(assignment)][(load load expression)][(load expression)]"); |
| } |
| |
| @Test |
| public void testChunksMaySpanFiles() { |
| assertThat( |
| split( |
| parse( |
| "x = 1", // |
| "load('m', 'y')"), |
| parse( |
| "z = 1", // |
| "load('m', 'y2')"))) |
| .isEqualTo("[(assignment)][(load)(assignment)][(load)]"); |
| } |
| |
| // Returns a string that indicates the breakdown of statements into chunks. |
| private static String split(StarlarkFile... files) { |
| StringBuilder buf = new StringBuilder(); |
| for (List<StarlarkFile> chunk : WorkspaceFileFunction.splitChunks(Arrays.asList(files))) { |
| buf.append('['); |
| for (StarlarkFile partialFile : chunk) { |
| buf.append('('); |
| String sep = ""; |
| for (Statement stmt : partialFile.getStatements()) { |
| buf.append(sep).append(Ascii.toLowerCase(stmt.kind().toString())); |
| sep = " "; |
| } |
| buf.append(')'); |
| } |
| buf.append(']'); |
| } |
| return buf.toString(); |
| } |
| |
| private static StarlarkFile parse(String... lines) { |
| return StarlarkFile.parse(ParserInput.fromLines(lines)); |
| } |
| } |