// 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 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.ManualClock; import com.google.devtools.build.lib.testutil.TestUtils; 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; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * 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. */ @RunWith(JUnit4.class) public class JavaIoFileSystemTest extends SymlinkAwareFileSystemTest { private ManualClock clock; @Override public FileSystem getFreshFileSystem() { clock = new ManualClock(); return new JavaIoFileSystem(clock); } // 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 testSetLastModifiedTime() throws Exception { Path file = xEmptyDirectory.getChild("new-file"); FileSystemUtils.createEmptyFile(file); file.setLastModifiedTime(1000L); assertThat(file.getLastModifiedTime()).isEqualTo(1000L); file.setLastModifiedTime(0L); assertThat(file.getLastModifiedTime()).isEqualTo(0L); clock.advanceMillis(42000L); file.setLastModifiedTime(-1L); assertThat(file.getLastModifiedTime()).isEqualTo(42000L); } @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. * *

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. * *

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> futures = new ArrayList<>(); for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex) { futures.add( executor.submit( () -> { try { for (int loopi = 0; loopi < directoryCreationCount; ++loopi) { List subDirs = getSubDirectories(xEmptyDirectory, loopi, subDirectoryCount); Path lastDir = Iterables.getLast(subDirs); FileSystemUtils.createDirectoryAndParents(lastDir); } } catch (IOException e) { return e; } return null; })); } ListenableFuture> all = Futures.allAsList(futures); // If the test times out here then there's likely to be a deadlock List exceptions = all.get(TestUtils.WAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS); Optional error = exceptions.stream().filter(Objects::nonNull).findFirst(); if (error.isPresent()) { throw error.get(); } } private static List getSubDirectories(Path base, int loopi, int subDirectoryCount) { Path path = base; List subDirs = new ArrayList<>(); for (int subDirIndex = 0; subDirIndex < subDirectoryCount; ++subDirIndex) { path = path.getChild(String.format("%d-%d", loopi, subDirIndex)); subDirs.add(path); } return subDirs; } }