|  | // 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.clock.BlazeClock; | 
|  | import com.google.devtools.build.lib.vfs.DigestHashFunction; | 
|  | 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.io.InputStream; | 
|  | 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(), DigestHashFunction.SHA256), "/"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new ScratchFileSystem using the {@link InMemoryFileSystem} | 
|  | */ | 
|  | public Scratch(String workingDir) { | 
|  | this(new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256), 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()) { | 
|  | dir.createDirectoryAndParents(); | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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; | 
|  | } | 
|  |  | 
|  | public String readFile(String pathName) throws IOException { | 
|  | try (InputStream in = resolve(pathName).getInputStream()) { | 
|  | return new String(ByteStreams.toByteArray(in), DEFAULT_CHARSET); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Like {@code scratch.file}, but the lines are added to the end if the file already exists. */ | 
|  | public Path appendFile(String pathName, Collection<String> lines) throws IOException { | 
|  | return appendFile(pathName, lines.toArray(new String[lines.size()])); | 
|  | } | 
|  |  | 
|  | /** Like {@code scratch.file}, but the lines are added to the end if the file already exists. */ | 
|  | public Path appendFile(String pathName, String... lines) throws IOException { | 
|  | return appendFile(pathName, DEFAULT_CHARSET, lines); | 
|  | } | 
|  |  | 
|  | /** Like {@code scratch.file}, but the lines are added to the end if the file already exists. */ | 
|  | public Path appendFile(String pathName, Charset charset, String... lines) throws IOException { | 
|  | Path path = resolve(pathName); | 
|  |  | 
|  | StringBuilder content = new StringBuilder(); | 
|  | if (path.exists()) { | 
|  | content.append(readFile(pathName)); | 
|  | content.append("\n"); | 
|  | } | 
|  | content.append(linesAsString(lines)); | 
|  |  | 
|  | return overwriteFile(pathName, content.toString()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(); | 
|  | } | 
|  |  | 
|  | /** 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()) { | 
|  | parentDir.createDirectoryAndParents(); | 
|  | } | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | public void copyFile(String sourceFile, String destFile) throws IOException { | 
|  | String contents = readFile(sourceFile); | 
|  | overwriteFile(destFile, contents); | 
|  | } | 
|  | } |