/*
* Copyright 2016-present Facebook, Inc.
*
* 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.facebook.buck.eden;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import com.facebook.buck.io.DefaultProjectFilesystemDelegate;
import com.facebook.buck.io.ProjectFilesystemDelegate;
import com.facebook.buck.util.sha1.Sha1HashCode;
import com.facebook.eden.thrift.EdenError;
import com.facebook.thrift.TException;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import org.junit.Test;
public class EdenProjectFilesystemDelegateTest {
private static final Sha1HashCode DUMMY_SHA1 = Sha1HashCode.of(Strings.repeat("faceb00c", 5));
/**
* This is the location of the working directory for {@link Configuration#unix()}. Creating
* symlinks via {@link Files#createSymbolicLink(Path, Path,
* java.nio.file.attribute.FileAttribute[])} in the working directory of Jimfs does not touch the
* actual filesystem.
*/
private static final String JIMFS_WORKING_DIRECTORY = "/work";
@Test
public void computeSha1ForOrdinaryFileUnderMount() throws IOException, EdenError, TException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath(JIMFS_WORKING_DIRECTORY);
ProjectFilesystemDelegate delegate = new DefaultProjectFilesystemDelegate(root);
EdenMount mount = createMock(EdenMount.class);
Path path = fs.getPath("foo/bar");
expect(mount.getBindMounts()).andReturn(ImmutableList.of());
expect(mount.getPathRelativeToProjectRoot(root.resolve(path))).andReturn(Optional.of(path));
expect(mount.getSha1(path)).andReturn(DUMMY_SHA1);
replay(mount);
EdenProjectFilesystemDelegate edenDelegate = new EdenProjectFilesystemDelegate(mount, delegate);
assertEquals(DUMMY_SHA1, edenDelegate.computeSha1(path));
verify(mount);
}
@Test
public void computeSha1ForOrdinaryFileUnderMountButBehindBindMount()
throws IOException, EdenError, TException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath(JIMFS_WORKING_DIRECTORY);
ProjectFilesystemDelegate delegate = new DefaultProjectFilesystemDelegate(root);
EdenMount mount = createMock(EdenMount.class);
Path path = fs.getPath("buck-out/gen/some-output");
Files.createDirectories(path.getParent());
Files.createFile(path);
byte[] bytes = new byte[] {66, 85, 67, 75};
Files.write(path, bytes);
expect(mount.getBindMounts()).andReturn(ImmutableList.of(fs.getPath("buck-out")));
expect(mount.getPathRelativeToProjectRoot(root.resolve(path))).andReturn(Optional.of(path));
replay(mount);
EdenProjectFilesystemDelegate edenDelegate = new EdenProjectFilesystemDelegate(mount, delegate);
assertEquals(
"EdenProjectFilesystemDelegate.computeSha1() should compute the SHA-1 directly via "
+ "DefaultProjectFilesystemDelegate because the path is behind a bind mount.",
Sha1HashCode.fromHashCode(Hashing.sha1().hashBytes(bytes)),
edenDelegate.computeSha1(path));
verify(mount);
}
@Test
public void computeSha1ForSymlinkUnderMountThatPointsToFileUnderMount()
throws EdenError, TException, IOException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath(JIMFS_WORKING_DIRECTORY);
ProjectFilesystemDelegate delegate = new DefaultProjectFilesystemDelegate(root);
// Create a symlink within the project root.
Path link = fs.getPath("/work/link");
Path target = fs.getPath("/work/target");
Files.createFile(target);
Files.createSymbolicLink(link, target);
// Eden will throw when the SHA-1 for the link is requested, but return a SHA-1 when the target
// is requested.
EdenMount mount = createMock(EdenMount.class);
expect(mount.getBindMounts()).andReturn(ImmutableList.of());
expect(mount.getPathRelativeToProjectRoot(link)).andReturn(Optional.of(fs.getPath("link")));
expect(mount.getPathRelativeToProjectRoot(target)).andReturn(Optional.of(fs.getPath("target")));
expect(mount.getSha1(fs.getPath("link"))).andThrow(new EdenError());
expect(mount.getSha1(fs.getPath("target"))).andReturn(DUMMY_SHA1);
replay(mount);
EdenProjectFilesystemDelegate edenDelegate = new EdenProjectFilesystemDelegate(mount, delegate);
assertEquals(DUMMY_SHA1, edenDelegate.computeSha1(link));
verify(mount);
}
@Test
public void computeSha1ForSymlinkUnderMountThatPointsToFileOutsideMount()
throws IOException, EdenError, TException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath(JIMFS_WORKING_DIRECTORY);
ProjectFilesystemDelegate delegate = new DefaultProjectFilesystemDelegate(root);
// Create a symlink within the project root.
Path link = fs.getPath("/work/link");
Path target = fs.getPath("/example");
Files.createFile(target);
byte[] bytes = new byte[] {66, 85, 67, 75};
Files.write(target, bytes);
Files.createSymbolicLink(link, target);
// Eden will throw when the SHA-1 for the link is requested, but return a SHA-1 when the target
// is requested.
EdenMount mount = createMock(EdenMount.class);
expect(mount.getBindMounts()).andReturn(ImmutableList.of());
expect(mount.getPathRelativeToProjectRoot(link)).andReturn(Optional.of(fs.getPath("link")));
expect(mount.getPathRelativeToProjectRoot(target)).andReturn(Optional.empty());
expect(mount.getSha1(fs.getPath("link"))).andThrow(new EdenError());
replay(mount);
EdenProjectFilesystemDelegate edenDelegate = new EdenProjectFilesystemDelegate(mount, delegate);
assertEquals(
"EdenProjectFilesystemDelegate.computeSha1() should return the SHA-1 of the target of "
+ "the symlink even though the target is outside of the EdenFS root.",
Sha1HashCode.fromHashCode(Hashing.sha1().hashBytes(bytes)),
edenDelegate.computeSha1(link));
verify(mount);
}
@Test
public void computeSha1ForOrdinaryFileOutsideMount() throws IOException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path root = fs.getPath(JIMFS_WORKING_DIRECTORY);
ProjectFilesystemDelegate delegate = new DefaultProjectFilesystemDelegate(root);
Path target = fs.getPath("/example");
Files.createFile(target);
byte[] bytes = new byte[] {66, 85, 67, 75};
Files.write(target, bytes);
EdenMount mount = createMock(EdenMount.class);
expect(mount.getBindMounts()).andReturn(ImmutableList.of());
expect(mount.getPathRelativeToProjectRoot(target)).andReturn(Optional.empty());
replay(mount);
EdenProjectFilesystemDelegate edenDelegate = new EdenProjectFilesystemDelegate(mount, delegate);
assertEquals(
"EdenProjectFilesystemDelegate.computeSha1() should return the SHA-1 of a file that is "
+ "outside of the EdenFS root.",
Sha1HashCode.fromHashCode(Hashing.sha1().hashBytes(bytes)),
edenDelegate.computeSha1(target));
verify(mount);
}
}