// 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.util; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.util.BlazeClock; import com.google.devtools.build.lib.util.StringUtilities; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; /** * Base class for a testing apparatus for a scratch filesystem. */ public class FsApparatus { /* ---------- State that the apparatus initializes / operates on --------- */ protected FileSystem fileSystem = null; protected Path workingDir = null; public static FsApparatus newInMemory() { return new FsApparatus(); } // TestUtil.getTmpDir is slow, so cache the result here private static final String TMP_DIR = new File(TestUtils.tmpDir(), "bs").toString(); /** * When using a Native file system, absolute paths will be treated as absolute paths on the unix * file system, as opposed to paths relative to the backing temp directory. So for simplicity, * you ought to only use relative paths for FsApparatus#file, FsApparatus#dir, and * FsApparatus#path. Otherwise, be aware of the following issue * * Path p1 = scratch.path(...); * Path p2 = scratch.path(p1.getPathString()); * * We'd like the invariant that p1.equals(p2) regardless if scratch is in-memory or not, but this * does not hold with our usage of Unix filesystems. */ public static FsApparatus newNative() { FileSystem fs = FileSystems.getNativeFileSystem(); Path wd = fs.getPath(TMP_DIR); try { FileSystemUtils.deleteTree(wd); } catch (IOException e) { throw new AssertionError(e.getMessage()); } return new FsApparatus(fs, wd); } private FsApparatus() { fileSystem = new InMemoryFileSystem(BlazeClock.instance()); workingDir = fileSystem.getPath("/"); } public FsApparatus(FileSystem fs, Path cwd) { fileSystem = fs; workingDir = cwd; } public FsApparatus(FileSystem fs) { fileSystem = fs; workingDir = fs.getPath("/"); } public FileSystem fs() { return fileSystem; } /** * Creates a scratch file in the scratch filesystem with the given {@code pathName} with * {@code lines} being its content. The method returns a Path instance for the scratch file. */ public Path file(String pathName, String... lines) throws IOException { Path file = path(pathName); Path parentDir = file.getParentDirectory(); if (!parentDir.exists()) { FileSystemUtils.createDirectoryAndParents(parentDir); } if (file.exists()) { throw new IOException("Could not create scratch file (file exists) " + file); } String fileContent = StringUtilities.joinLines(lines); FileSystemUtils.writeContentAsLatin1(file, fileContent); return file; } /** * Creates or recreates a scratch file just like {@link #file} but tolerating an existing file. */ public Path overwriteFile(String pathName, String... lines) throws IOException { try { path(pathName).delete(); } catch (FileNotFoundException e) { // Ignored. } return file(pathName, lines); } /** * Initializes this apparatus (if it hasn't been initialized yet), and creates * a directory in the scratch filesystem, with the given {@code pathName}. * Creates parent directories as necessary. */ public Path dir(String pathName) throws IOException { Path dir = path(pathName); if (!dir.exists()) { FileSystemUtils.createDirectoryAndParents(dir); } if (!dir.isDirectory()) { throw new IOException("Exists, but is not a directory: " + dir); } return dir; } /** * Resolves {@code pathName} relative to the working directory. Note that this will not create any * entity in the filesystem; i.e., the file that the object is describing may not exist in the * filesystem. */ public Path path(String pathName) { return workingDir.getRelative(pathName); } /** * Create a fresh directory in the system temporary directory, instead of the * testing directory provided by the testing framework. This path is usually * shorter than a path starting with TestUtil.getTmpDir(). We care about the * length because of the path length restriction for Unix local socket files. * * Clients are responsible for deleting the directory after tests. */ public Path createUnixTempDir() throws IOException { if (fileSystem instanceof InMemoryFileSystem) { throw new IOException("Can not create Unix temporary directories in " + "an in-memory file system"); } File file = File.createTempFile("scratch", "tmp"); final Path path = fileSystem.getPath(file.getAbsolutePath()); path.delete(); path.createDirectory(); return path; } }