Forwarding File System
A FileSystem that forwards calls to another, intended for subclassing.
Fault Injection
You can use this to deterministically trigger file system failures in tests. This is useful to confirm that your program behaves correctly even if its file system operations fail. For example, this subclass fails every access of files named unlucky.txt
:
val faultyFileSystem = object : ForwardingFileSystem(FileSystem.SYSTEM) {
override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
if (path.name == "unlucky.txt") throw IOException("synthetic failure!")
return path
}
}
You can fail specific operations by overriding them directly:
val faultyFileSystem = object : ForwardingFileSystem(FileSystem.SYSTEM) {
override fun delete(path: Path) {
throw IOException("synthetic failure!")
}
}
Observability
You can extend this to verify which files your program accesses. This is a testing file system that records accesses as they happen:
class LoggingFileSystem : ForwardingFileSystem(FileSystem.SYSTEM) {
val log = mutableListOf<String>()
override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path {
log += "$functionName($parameterName=$path)"
return path
}
}
This makes it easy for tests to assert exactly which files were accessed.
@Test
fun testMergeJsonReports() {
createSampleJsonReports()
loggingFileSystem.log.clear()
mergeJsonReports()
assertThat(loggingFileSystem.log).containsExactly(
"list(dir=json_reports)",
"source(file=json_reports/2020-10.json)",
"source(file=json_reports/2020-12.json)",
"source(file=json_reports/2020-11.json)",
"sink(file=json_reports/2020-all.json)"
)
}
Transformations
Subclasses can transform file names and content.
For example, your program may be written to operate on a well-known directory like /etc/
or /System
. You can rewrite paths to make such operations safer to test.
You may also transform file content to apply application-layer encryption or compression. This is particularly useful in situations where it's difficult or impossible to enable those features in the underlying file system.
Abstract Functions Only
Some file system functions like copy are implemented by using other features. These are the non-abstract functions in the FileSystem interface.
This class forwards only the abstract functions; non-abstract functions delegate to the other functions of this class. If desired, subclasses may override non-abstract functions to forward them.
Properties
FileSystem to which this instance is delegating.
Functions
Returns a sink that appends bytes to the end of file, creating it if it doesn't already exist.
Creates a directory at the path identified by dir, and any enclosing parent path directories, recursively.
Creates a directory at the path identified by dir.
Recursively deletes all children of fileOrDirectory if it is a directory, then deletes fileOrDirectory itself.
Returns metadata of the file, directory, or object identified by path.
Returns a handle to read file. This will fail if the file doesn't already exist.
Returns a handle to read and write file. This will create the file if it doesn't already exist.
Creates a source to read file, executes readerAction to read it, and then closes the source. This is a compact way to read the contents of a file.
Creates a sink to write file, executes writerAction to write it, and then closes the sink. This is a compact way to write a file.