blob: f6f4ef5d3fb21431c5d3c7bbca3516c83b131b95 [file]
// Copyright 2014 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.vfs;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.lib.testutil.TestUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
/**
* Tests for the {@link JavaIoFileSystem}. That file system by itself is not capable of creating
* symlinks; use the unix one to create them, so that the test can check that the file system
* handles their existence correctly.
*/
public class JavaIoFileSystemTest extends SymlinkAwareFileSystemTest {
@Override
public FileSystem getFreshFileSystem(DigestHashFunction digestHashFunction) {
return new JavaIoFileSystem(digestHashFunction);
}
// Tests are inherited from the FileSystemTest
// JavaIoFileSystem incorrectly throws a FileNotFoundException for all IO errors. This means that
// statIfFound incorrectly suppresses those errors.
@Override
@Test
public void testBadPermissionsThrowsExceptionOnStatIfFound() {}
@Test
public void testInvalidPathWithNulCharacterHandling() throws Exception {
FileSystem testFs = getFreshFileSystem(DigestHashFunction.SHA256);
PathFragment invalidPath = PathFragment.create("/foo/bar\0bad");
Path path = testFs.getPath(invalidPath);
assertThat(path.exists()).isFalse();
assertThat(path.isDirectory()).isFalse();
assertThat(path.isFile()).isFalse();
assertThat(path.isSymbolicLink()).isFalse();
assertThat(path.statIfFound()).isNull();
assertThat(path.statIfFound(Symlinks.FOLLOW)).isNull();
assertThat(path.statIfFound(Symlinks.NOFOLLOW)).isNull();
assertThat(testFs.statIfFound(invalidPath, /* followSymlinks= */ true)).isNull();
assertThat(testFs.statIfFound(invalidPath, /* followSymlinks= */ false)).isNull();
assertThat(path.delete()).isFalse();
assertThrows(FileNotFoundException.class, () -> path.stat());
assertThrows(FileNotFoundException.class, () -> path.getDirectoryEntries());
assertThrows(FileNotFoundException.class, () -> path.readSymbolicLink());
assertThrows(IOException.class, () -> testFs.createDirectory(invalidPath));
assertThrows(IOException.class, () -> path.createDirectoryAndParents());
assertThrows(IOException.class, () -> path.createSymbolicLink(testFs.getPath("/target")));
assertThrows(
IOException.class,
() -> testFs.createFSDependentHardLink(invalidPath, PathFragment.create("/target")));
assertThrows(
IOException.class, () -> testFs.renameTo(invalidPath, PathFragment.create("/foo/dest")));
assertThrows(
IOException.class, () -> testFs.renameTo(PathFragment.create("/foo/src"), invalidPath));
assertThrows(IOException.class, () -> path.createReadWriteByteChannel());
assertThrows(IOException.class, () -> testFs.createReadWriteByteChannel(invalidPath));
}
@Override
protected boolean isHardLinked(Path a, Path b) throws IOException {
return Files.readAttributes(
Paths.get(a.toString()), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)
.fileKey()
.equals(
Files.readAttributes(
Paths.get(b.toString()), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)
.fileKey());
}
/**
* This test has a large number of threads racing to create the same subdirectories.
*
* <p>We create N number of distinct directory trees, eg. the tree "0-0/0-1/0-2/0-3/0-4" followed
* by the tree "1-0/1-1/1-2/1-3/1-4" etc. If there is race we should quickly get a deadlock.
*
* <p>A timeout of this test is likely because of a deadlock.
*/
@Test
public void testCreateDirectoriesThreadSafety() throws Exception {
int threadCount = 200;
int directoryCreationCount = 500; // We create this many sets of directories
int subDirectoryCount = 5; // Each directory tree is this deep
ListeningExecutorService executor =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadCount));
List<ListenableFuture<IOException>> futures = new ArrayList<>();
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
futures.add(
executor.submit(
() -> {
try {
for (int loopi = 0; loopi < directoryCreationCount; ++loopi) {
List<Path> subDirs =
getSubDirectories(xEmptyDirectory, loopi, subDirectoryCount);
Path lastDir = Iterables.getLast(subDirs);
lastDir.createDirectoryAndParents();
}
} catch (IOException e) {
return e;
}
return null;
}));
}
ListenableFuture<List<IOException>> all = Futures.allAsList(futures);
// If the test times out here then there's likely to be a deadlock
List<IOException> exceptions =
all.get(TestUtils.WAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
Optional<IOException> error = exceptions.stream().filter(Objects::nonNull).findFirst();
if (error.isPresent()) {
throw error.get();
}
}
private static List<Path> getSubDirectories(Path base, int loopi, int subDirectoryCount) {
Path path = base;
List<Path> subDirs = new ArrayList<>();
for (int subDirIndex = 0; subDirIndex < subDirectoryCount; ++subDirIndex) {
path = path.getChild(String.format("%d-%d", loopi, subDirIndex));
subDirs.add(path);
}
return subDirs;
}
}