org.evosuite.runtime.vfs.VirtualFileSystem Maven / Gradle / Ivy
/**
* Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see .
*/
package org.evosuite.runtime.vfs;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import org.evosuite.runtime.testdata.EvoSuiteFile;
import org.evosuite.runtime.LeakingResource;
import org.evosuite.runtime.sandbox.MSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A virtual file system (VFS) to handle the files accessed/generated by the
* test cases. No file is ever written to disk.
*
*
* The VFS reflects the actual OS in which the JVM is running (eg Windows vs
* Unix)
*
*
* This class is also used to keep track of what the test cases have tried to
* do. This is useful to guide the search.
*
*
* @author arcuri
*
*/
public final class VirtualFileSystem {
private static final Logger logger = LoggerFactory.getLogger(VirtualFileSystem.class);
/**
* The only instance of this class
*/
private static final VirtualFileSystem singleton = new VirtualFileSystem();
/**
* The root of the VFS
*
*
* TODO: we might need to simulate more than one root on same FS
*/
private VFolder root;
/**
* An atomic counter for generating unique names for tmp files
*/
private final AtomicInteger tmpFileCounter;
/**
* Regular files that are accessed during the search. Tmp files are not
* considered, as they are not interesting from a point of view of
* generating test data.
*
*
* Ideally we should only keep track of what the SUT reads, and not the
* files it generates. However, for testing purposes, if SUT tries to write
* to file X, then it would be still important to consider the case in which
* X already exists.
*/
private final Set accessedFiles;
/**
* Check if all operations in this VFS should throw IOException
*/
private volatile boolean shouldAllThrowIOException;
/**
* The classes in this set are marked to throw IOException
*/
private final Set classesThatShouldThrowIOException;
/**
* A set of all IO leaking resources (eg streams) created during the search
*
*/
private final Set leakingResources;
//--------------------------------------------------------------------------
/**
* Hidden, main constructor
*/
private VirtualFileSystem() {
tmpFileCounter = new AtomicInteger(0);
accessedFiles = new CopyOnWriteArraySet<>(); //we only add during test execution, and read after
leakingResources = new CopyOnWriteArraySet<>();
classesThatShouldThrowIOException = new CopyOnWriteArraySet<>(); //should only contain very few values
}
/**
* Get the final instance of this singleton
*
* @return
*/
public static VirtualFileSystem getInstance() {
return singleton;
}
/**
* Reset the internal state of this singleton
*/
public void resetSingleton() {
root = null;
tmpFileCounter.set(0);
accessedFiles.clear();
shouldAllThrowIOException = false;
classesThatShouldThrowIOException.clear();
synchronized(leakingResources){
for(LeakingResource resource : leakingResources){
try{
resource.release();
} catch(Exception e){
logger.warn("Failed to release resource: "+e.getMessage(),e);
}
}
leakingResources.clear();
}
}
/**
* Add a leaking resource to this VFS.
* This is mainly necessary for stream objects, even if they are virtual
*
* @param resource
*/
public void addLeakingResource(LeakingResource resource){
synchronized(leakingResources){
leakingResources.add(resource);
}
}
/**
* Some file streams open file connections in their constructors. Even if we
* use a VFS, those methods have to be called. Therefore, we create a single
* tmp file which will be used to deceive those constructors
*
* @return
*/
public File getRealTmpFile() {
return MSecurityManager.getRealTmpFile();
}
public void throwSimuledIOExceptionIfNeeded(String path) throws IOException {
if (isClassSupposedToThrowIOException(path)) {
throw new IOException("Simulated IOException");
}
}
public boolean isClassSupposedToThrowIOException(String path) {
if (shouldAllThrowIOException || classesThatShouldThrowIOException.contains(path)) {
return true;
}
return false;
}
public boolean setShouldThrowIOException(EvoSuiteFile file) {
String path = file.getPath();
if (classesThatShouldThrowIOException.contains(path)) {
return false;
}
classesThatShouldThrowIOException.add(path);
return true;
}
/**
* All operations in the entire VFS will throw an IOException if that
* appears in their method signature
*/
public boolean setShouldAllThrowIOExceptions() {
if (shouldAllThrowIOException) {
return false;
}
shouldAllThrowIOException = true;
return true;
}
/**
* Initialize the virtual file system with the current directory the JVM was
* started from
*/
public void init() {
root = new VFolder(null, null);
String workingDir = getWorkingDirPath();
createFolder(workingDir);
createFolder(getTmpFolderPath());
//important to clear, as above code would modify this field
accessedFiles.clear();
}
public static String getWorkingDirPath(){
//this should be set in the scaffolding file
return java.lang.System.getProperty("user.dir");
}
public static String getDefaultParent(){
//TODO this need more checking/validation
return File.separator;
}
private void markAccessedFile(String path) {
if (path.contains("\"")) {
//shouldn't really have paths with ". Furthermore, " would mess up the writing of JUnit files
return;
}
accessedFiles.add(path);
}
/**
* For each file that has been accessed during the search, keep track of it
*
* @return a set of file paths
*/
public Set getAccessedFiles() {
return new HashSet(accessedFiles);
}
/**
* Create a tmp file, and return its absolute path
*
* @param prefix
* @param suffix
* @param directory
* @return {@code null} if file could not be created
* @throws IOException
*/
public String createTempFile(String prefix, String suffix, File directory)
throws IllegalArgumentException, IOException {
if (prefix.length() < 3)
throw new IllegalArgumentException("Prefix string too short");
if (suffix == null)
suffix = ".tmp";
String folder = null;
if (directory == null) {
folder = getTmpFolderPath();
} else {
folder = directory.getAbsolutePath();
}
int counter = tmpFileCounter.getAndIncrement();
String fileName = prefix + counter + suffix;
String path = folder + File.separator + fileName;
boolean created = createFile(path);
if (!created) {
throw new IOException();
}
return path;
}
private String getTmpFolderPath() {
return java.lang.System.getProperty("java.io.tmpdir");
}
public boolean exists(String rawPath) {
return findFSObject(rawPath) != null;
}
/**
* Return the VFS object represented by the given {@code rowPath}.
*
* @param rawPath
* @return {@code null} if the object does not exist in the VFS
*/
public FSObject findFSObject(String rawPath) {
String path = new File(rawPath).getAbsolutePath();
String[] tokens = tokenize(path);
markAccessedFile(path);
VFolder parent = root;
for (int i = 0; i < tokens.length; i++) {
String name = tokens[i];
FSObject child = parent.getChild(name);
//child might not exist
if (child == null || child.isDeleted()) {
return null;
}
//we might end up with a file on the path that is not a folder
if (i < (tokens.length - 1) && !child.isFolder()) {
return null;
} else {
if (i == (tokens.length - 1)) {
return child;
}
}
parent = (VFolder) child;
}
return parent;
}
public boolean deleteFSObject(String rawPath) {
FSObject obj = findFSObject(rawPath);
if (obj == null || !obj.isWritePermission()) {
return false;
}
return obj.delete();
}
public boolean createFile(String rawPath) {
return createFile(rawPath, false);
}
private boolean createFile(String rawPath, boolean tmp) {
String parent = new File(rawPath).getParent();
boolean created = createFolder(parent);
if (!created) {
return false;
}
VFolder folder = (VFolder) findFSObject(parent);
VFile file = new VFile(rawPath, folder);
folder.addChild(file);
if (!tmp) {
markAccessedFile(file.getPath());
}
return true;
}
public boolean rename(String source, String destination) {
String parentSource = new File(source).getParent();
String parentDest = new File(destination).getParent();
if ((parentSource == null && parentDest != null)
|| (!parentSource.equals(parentDest))) {
//both need to be in the same folder
return false;
}
FSObject src = findFSObject(source);
if (src == null) {
//source should exist
return false;
}
FSObject dest = findFSObject(destination);
if (dest != null) {
//destination should not exist
return false;
}
return src.rename(destination);
}
public boolean createFolder(String rawPath) {
String[] tokens = tokenize(new File(rawPath).getAbsolutePath());
VFolder parent = root;
for (String name : tokens) {
if (!parent.isReadPermission() || !parent.isWritePermission()
|| parent.isDeleted()) {
return false;
}
VFolder folder = null;
if (!parent.hasChild(name)) {
String path = null;
if(isUnixStyle() || !parent.isRoot()){
path = parent.getPath() + File.separator + name;
} else {
//this means we are in Windows, and that we are considering a direct child of the root
path = name;
}
folder = new VFolder(path, parent);
} else {
FSObject child = parent.getChild(name);
if (!child.isFolder()) {
return false;
}
folder = (VFolder) child;
}
parent.addChild(folder);
parent = folder;
}
markAccessedFile(parent.getPath());
return true;
}
private String[] tokenize(String path){
return tokenize(path,File.separatorChar);
}
protected static String[] tokenize(String path, char separator) {
String[] tokens = path.split(separator == '\\' ? "\\\\" : File.separator);
List list = new ArrayList(tokens.length);
for (String token : tokens) {
if (!token.isEmpty()) {
list.add(token);
}
}
return list.toArray(new String[0]);
}
private boolean isUnixStyle() {
return File.separator.equals("/");
}
}