// 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.expectThrows; import static org.junit.Assert.fail; 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) { try { locator.getPackageBuildFile(PackageIdentifier.createInMainRepo(packageName)); fail(); } catch (NoSuchPackageException e) { String message = e.getMessage(); assertThat(message) .containsMatch(Pattern.compile(Pattern.quote(expectedError), Pattern.CASE_INSENSITIVE)); } } @Test public void testGetPackageBuildFile() throws Exception { AtomicReference 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: expectThrows( 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 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()).isSameAs(0); PathPackageLocator.create( null, ImmutableList.of("./foo"), reporter, workspace, workspace.getRelative("foo"), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); assertThat(eventCollector.count()).isSameAs(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); } }