blob: bf2621adaacea1c684ab4d4cce912ec086ea431c [file] [log] [blame]
// 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.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()), "/");
}
/**
* 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;
}
/**
* 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()) {
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();
}
}