// 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.testutil; import com.google.common.io.ByteStreams; import com.google.devtools.build.lib.util.BlazeClock; 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.PathFragment; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collection; /** * Allow tests to easily manage scratch files in a FileSystem. */ public final class Scratch { private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; private final FileSystem fileSystem; private Path workingDir = null; /** * Create a new ScratchFileSystem using the {@link InMemoryFileSystem} */ public Scratch() { this(new InMemoryFileSystem(BlazeClock.instance()), "/"); } /** * Create a new ScratchFileSystem using the {@link InMemoryFileSystem} */ public Scratch(String workingDir) { this(new InMemoryFileSystem(BlazeClock.instance()), workingDir); } /** * Create a new ScratchFileSystem using the given {@code Path}. */ public Scratch(Path workingDir) { this.fileSystem = workingDir.getFileSystem(); this.workingDir = workingDir; } /** * Create a new ScratchFileSystem using the supplied FileSystem. */ public Scratch(FileSystem fileSystem) { this(fileSystem, "/"); } /** * Create a new ScratchFileSystem using the supplied FileSystem. */ public Scratch(FileSystem fileSystem, String workingDir) { this.fileSystem = fileSystem; this.workingDir = fileSystem.getPath(workingDir); } /** * Returns the FileSystem in use. */ public FileSystem getFileSystem() { return fileSystem; } public void setWorkingDir(String workingDir) { this.workingDir = fileSystem.getPath(workingDir); } /** * 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 resolve(String pathName) { return workingDir.getRelative(pathName); } /** * 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 resolve(PathFragment pathName) { return workingDir.getRelative(pathName); } /** * Create a directory in the scratch filesystem, with the given path name. */ public Path dir(String pathName) throws IOException { Path dir = resolve(pathName); if (!dir.exists()) { FileSystemUtils.createDirectoryAndParents(dir); } if (!dir.isDirectory()) { throw new IOException("Exists, but is not a directory: " + pathName); } return dir; } public Path file(String pathName, String... lines) throws IOException { return file(pathName, DEFAULT_CHARSET, lines); } /** * Create a scratch file in the scratch filesystem, with the given pathName, * consisting of a set of lines. The method returns a Path instance for the * scratch file. */ public Path file(String pathName, Charset charset, String... lines) throws IOException { Path file = newFile(pathName); FileSystemUtils.writeContent(file, charset, linesAsString(lines)); file.setLastModifiedTime(-1L); return file; } public String readFile(String pathName) throws IOException { return new String( ByteStreams.toByteArray(resolve(pathName).getInputStream()), DEFAULT_CHARSET); } /** * Like {@code scratch.file}, but the file is first deleted if it already * exists. */ public Path overwriteFile(String pathName, Collection<String> lines) throws IOException { return overwriteFile(pathName, lines.toArray(new String[lines.size()])); } /** * Like {@code scratch.file}, but the file is first deleted if it already * exists. */ public Path overwriteFile(String pathName, String... lines) throws IOException { return overwriteFile(pathName, DEFAULT_CHARSET, lines); } /** * Like {@code scratch.file}, but the file is first deleted if it already * exists. */ public Path overwriteFile(String pathName, Charset charset, String... lines) throws IOException { Path oldFile = resolve(pathName); long newMTime = oldFile.exists() ? oldFile.getLastModifiedTime() + 1 : -1; oldFile.delete(); Path newFile = file(pathName, charset, lines); newFile.setLastModifiedTime(newMTime); return newFile; } /** * Deletes the specified scratch file, using the same specification as {@link Path#delete}. */ public boolean deleteFile(String pathName) throws IOException { return resolve(pathName).delete(); } /** * Create a scratch file in the given filesystem, with the given pathName, * consisting of a set of lines. The method returns a Path instance for the * scratch file. */ public Path file(String pathName, byte[] content) throws IOException { Path file = newFile(pathName); FileSystemUtils.writeContent(file, content); return file; } /** Creates a new scratch file, ensuring parents exist. */ private Path newFile(String pathName) throws IOException { Path file = resolve(pathName); Path parentDir = file.getParentDirectory(); if (!parentDir.exists()) { FileSystemUtils.createDirectoryAndParents(parentDir); } if (file.exists()) { throw new IOException("Could not create scratch file (file exists) " + pathName); } return file; } /** * Converts the lines into a String with linebreaks. Useful for creating * in-memory input for a file, for example. */ private static String linesAsString(String... lines) { StringBuilder builder = new StringBuilder(); for (String line : lines) { builder.append(line); builder.append('\n'); } return builder.toString(); } }