| // 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.collect.ImmutableList; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.packages.NoSuchPackageException; |
| import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants; |
| import com.google.devtools.build.lib.testutil.FoundationTestCase; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.Root; |
| import com.google.devtools.build.lib.vfs.UnixGlob; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.regex.Pattern; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Test package-path logic. |
| */ |
| @RunWith(JUnit4.class) |
| public class PathPackageLocatorTest extends FoundationTestCase { |
| private Path buildBazelFile1A; |
| private Path buildFile1B; |
| private Path buildFile2C; |
| private Path buildFile2CD; |
| private Path buildFile2F; |
| private Path buildFile2FGH; |
| private Path buildBazelFile3A; |
| private Path buildFile3B; |
| private Path buildFile3CI; |
| private Path rootDir1; |
| private Path rootDir1WorkspaceFile; |
| private Path rootDir2; |
| private Path rootDir3ParentParent; |
| private Path rootDir3; |
| private Path rootDir4Parent; |
| private Path rootDir4; |
| private Path rootDir5; |
| private PathPackageLocator locator; |
| private PathPackageLocator locatorWithSymlinks; |
| |
| protected PathPackageLocator getLocator() { |
| return locator; |
| } |
| |
| @Before |
| public final void createFiles() throws Exception { |
| // Root 1: |
| // WORKSPACE |
| // /A/BUILD.bazel // This is the actual buildfile for this package. |
| // /A/BUILD // This is a dummy buildfile and isn't used. |
| // /B/BUILD |
| // /C/I/BUILD |
| // /C/D |
| // /C/E |
| // /F/G // This is a file, not a directory. |
| // |
| // Root 2: |
| // WORKSPACE |
| // /B/BUILD |
| // /C/BUILD |
| // /C/D/BUILD |
| // /F/BUILD |
| // /F/G |
| // /F/G/H/BUILD |
| // /I/BUILD // This is a directory, not a file. |
| // |
| // Root 3: |
| // /usr/local/google/jrluser-foo/READONLY -> root4 |
| // |
| // Root 4 (not used as a package root, but root 3 points to this) |
| // /A -> root1/A |
| // /B/BUILD -> root1/B/BUILD |
| // /C/I/BUILD -> root1/C/I/BUILD |
| // /C/D -> root1/C/D |
| // /C/E -> root1/C/E |
| // /F/G -> root1/F/G |
| // /H/I -> root5/H/I |
| // |
| // Root 5 (pointed to by Root 4) |
| // Note: the following BUILD file will be found if explicitly specified, but it |
| // would not be found if using wildcards. That is because isDirectory |
| // will return false since the symlink target is not in the workspace. |
| // /H/I/BUILD |
| rootDir1 = scratch.resolve("/home/user/src-foo/workspace"); |
| rootDir2 = scratch.resolve("/somewhere/1234567/build/workspace"); |
| rootDir3ParentParent = scratch.resolve("/usr/local/google/jrluser-foo"); |
| rootDir3 = rootDir3ParentParent.getRelative("READONLY/workspace"); |
| rootDir4Parent = scratch.resolve("/usr/local/symlinks/client_symlink_jrluser-foo"); |
| rootDir4 = rootDir4Parent.getRelative("workspace"); |
| rootDir5 = scratch.resolve("/foo/bar"); |
| |
| rootDir1WorkspaceFile = scratch.file(rootDir1 + "/WORKSPACE"); |
| buildBazelFile1A = createBuildFile(rootDir1, "A", true); |
| buildFile1B = createBuildFile(rootDir1, "B"); |
| createBuildFile(rootDir1, "C/I"); |
| scratch.file(rootDir1.getPathString() + "/F/G"); |
| |
| rootDir1.getRelative("C").createDirectory(); |
| rootDir1.getRelative("C/D").createDirectory(); |
| rootDir1.getRelative("C/E").createDirectory(); |
| |
| // Workspace file in rootDir2. |
| scratch.file(rootDir2 + "/WORKSPACE"); |
| createBuildFile(rootDir2, "B"); |
| buildFile2C = createBuildFile(rootDir2, "C"); |
| buildFile2CD = createBuildFile(rootDir2, "C/D"); |
| buildFile2F = createBuildFile(rootDir2, "F"); |
| buildFile2FGH = createBuildFile(rootDir2, "F/G/H"); |
| scratch.file(rootDir2.getPathString() + "/C/I"); |
| |
| // Root3 just needs a symlink to 4 |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir3ParentParent.getRelative("READONLY"), rootDir4Parent); |
| buildBazelFile3A = rootDir3.getRelative("A/BUILD.bazel"); |
| buildFile3B = rootDir3.getRelative("B/BUILD"); |
| buildFile3CI = rootDir3.getRelative("C/I/BUILD"); |
| |
| // Root4 |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("A"), rootDir1.getRelative("A")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("B/BUILD"), rootDir1.getRelative("B/BUILD")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("C/I/BUILD"), rootDir1.getRelative("C/I/BUILD")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("C/D/BUILD"), rootDir1.getRelative("C/D/BUILD")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("C/E/BUILD"), rootDir1.getRelative("C/E/BUILD")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("F/G/BUILD"), rootDir1.getRelative("F/G/BUILD")); |
| FileSystemUtils.ensureSymbolicLink( |
| rootDir4.getRelative("H/I"), rootDir5.getRelative("H/I")); |
| |
| // Root5 |
| createBuildFile(rootDir5, "H/I"); |
| |
| locator = |
| new PathPackageLocator( |
| outputBase, |
| ImmutableList.of(Root.fromPath(rootDir1), Root.fromPath(rootDir2)), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| locatorWithSymlinks = |
| new PathPackageLocator( |
| outputBase, |
| ImmutableList.of(Root.fromPath(rootDir3)), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| } |
| |
| private Path createBuildFile(Path workspace, String packageName) throws IOException { |
| return createBuildFile(workspace, packageName, false); |
| } |
| |
| private Path createBuildFile(Path workspace, String packageName, boolean dotBazel) |
| throws IOException { |
| String buildFileName = dotBazel ? "BUILD.bazel" : "BUILD"; |
| return scratch.file(workspace + "/" + packageName + "/" + buildFileName); |
| } |
| |
| private void checkFails(String packageName, String expectedError) { |
| checkFails(getLocator(), packageName, expectedError); |
| } |
| |
| private static void checkFails( |
| PathPackageLocator locator, String packageName, String expectedError) { |
| NoSuchPackageException e = |
| assertThrows( |
| NoSuchPackageException.class, |
| () -> locator.getPackageBuildFile(PackageIdentifier.createInMainRepo(packageName))); |
| String message = e.getMessage(); |
| assertThat(message) |
| .containsMatch(Pattern.compile(Pattern.quote(expectedError), Pattern.CASE_INSENSITIVE)); |
| } |
| |
| @Test |
| public void testGetPackageBuildFile() throws Exception { |
| AtomicReference<? extends UnixGlob.FilesystemCalls> cache = UnixGlob.DEFAULT_SYSCALLS_REF; |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("A"))) |
| .isEqualTo(buildBazelFile1A); |
| assertThat(locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("A"), cache)) |
| .isEqualTo(buildBazelFile1A); |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("B"))) |
| .isEqualTo(buildFile1B); |
| assertThat(locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("B"), cache)) |
| .isEqualTo(buildFile1B); |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("C"))) |
| .isEqualTo(buildFile2C); |
| assertThat(locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("C"), cache)) |
| .isEqualTo(buildFile2C); |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("C/D"))) |
| .isEqualTo(buildFile2CD); |
| assertThat( |
| locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("C/D"), cache)) |
| .isEqualTo(buildFile2CD); |
| checkFails("C/E", |
| "no such package 'C/E': BUILD file not found on package path"); |
| assertThat( |
| locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("C/E"), cache)) |
| .isNull(); |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("F"))) |
| .isEqualTo(buildFile2F); |
| checkFails("F/G", |
| "no such package 'F/G': BUILD file not found on package path"); |
| assertThat( |
| locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("F/G"), cache)) |
| .isNull(); |
| assertThat(locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("F/G/H"))) |
| .isEqualTo(buildFile2FGH); |
| assertThat( |
| locator.getPackageBuildFileNullable(PackageIdentifier.createInMainRepo("F/G/H"), cache)) |
| .isEqualTo(buildFile2FGH); |
| checkFails("I", "no such package 'I': BUILD file not found on package path"); |
| } |
| |
| @Test |
| public void testGetPackageBuildFileWithSymlinks() throws Exception { |
| assertThat(locatorWithSymlinks.getPackageBuildFile(PackageIdentifier.createInMainRepo("A"))) |
| .isEqualTo(buildBazelFile3A); |
| assertThat(locatorWithSymlinks.getPackageBuildFile(PackageIdentifier.createInMainRepo("B"))) |
| .isEqualTo(buildFile3B); |
| assertThat(locatorWithSymlinks.getPackageBuildFile(PackageIdentifier.createInMainRepo("C/I"))) |
| .isEqualTo(buildFile3CI); |
| checkFails( |
| locatorWithSymlinks, "C/D", "no such package 'C/D': BUILD file not found on package path"); |
| } |
| |
| @Test |
| public void testGetWorkspaceFile() throws Exception { |
| assertThat(locator.getWorkspaceFile()).isEqualTo(rootDir1WorkspaceFile); |
| } |
| |
| private Path setLocator(String root) { |
| Path nonExistentRoot = scratch.resolve(root); |
| this.locator = |
| PathPackageLocator.create( |
| null, |
| Arrays.asList(root), |
| reporter, |
| /*workspace=*/ FileSystemUtils.getWorkingDirectory(scratch.getFileSystem()), |
| /* clientWorkingDirectory= */ FileSystemUtils.getWorkingDirectory( |
| scratch.getFileSystem()), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| return nonExistentRoot; |
| } |
| |
| @Test |
| public void testExists() throws Exception { |
| Path nonExistentRoot1 = setLocator("/non/existent/1/workspace"); |
| // Now let's create the root: |
| createBuildFile(nonExistentRoot1, "X"); |
| // The package isn't found |
| // The package is found, because we didn't drop the root: |
| assertThrows( |
| NoSuchPackageException.class, |
| () -> locator.getPackageBuildFile(PackageIdentifier.createInMainRepo("X"))); |
| |
| Path nonExistentRoot2 = setLocator("/non/existent/2/workspace"); |
| // Now let's create the root: |
| createBuildFile(nonExistentRoot2, "X"); |
| // ...but the package is still not found, because we dropped the root: |
| checkFails("X", |
| "no such package 'X': BUILD file not found on package path"); |
| } |
| |
| @Test |
| public void testPathResolution() throws Exception { |
| Path workspace = scratch.dir("/some/path/to/workspace"); |
| Path clientPath = workspace.getRelative("somewhere/below/workspace"); |
| scratch.dir(clientPath.getPathString()); |
| Path belowClient = clientPath.getRelative("below/client"); |
| scratch.dir(belowClient.getPathString()); |
| |
| List<String> pathElements = ImmutableList.of( |
| "./below/client", // Client-relative |
| ".", // Client-relative |
| "%workspace%/somewhere", // Workspace-relative |
| // Absolute |
| clientPath.getRelative("below").getPathString()); |
| assertThat( |
| PathPackageLocator.create( |
| null, |
| pathElements, |
| reporter, |
| workspace, |
| clientPath, |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY) |
| .getPathEntries()) |
| .containsExactly( |
| Root.fromPath(belowClient), |
| Root.fromPath(clientPath), |
| Root.fromPath(workspace.getRelative("somewhere")), |
| Root.fromPath(clientPath.getRelative("below"))) |
| .inOrder(); |
| } |
| |
| @Test |
| public void testRelativePathWarning() throws Exception { |
| Path workspace = scratch.dir("/some/path/to/workspace"); |
| |
| // No warning if workspace == cwd. |
| PathPackageLocator.create( |
| null, |
| ImmutableList.of("./foo"), |
| reporter, |
| workspace, |
| workspace, |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| assertThat(eventCollector.count()).isSameInstanceAs(0); |
| |
| PathPackageLocator.create( |
| null, |
| ImmutableList.of("./foo"), |
| reporter, |
| workspace, |
| workspace.getRelative("foo"), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| assertThat(eventCollector.count()).isSameInstanceAs(1); |
| assertContainsEvent("The package path element 'foo' will be taken relative"); |
| } |
| |
| /** Regression test for bug: "IllegalArgumentException in PathPackageLocator.create()" */ |
| @Test |
| public void testDollarSigns() throws Exception { |
| Path workspace = scratch.dir("/some/path/to/workspace$1"); |
| |
| PathPackageLocator.create( |
| null, |
| ImmutableList.of("%workspace%/blabla"), |
| reporter, |
| workspace, |
| workspace.getRelative("foo"), |
| BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); |
| } |
| } |