Question

I'm using a 3rd party library which basically creates an output directory with different kinds of files and subdirectories inside. I would like to be able to write unit tests to confirm that the output is correct.

I would like to be able to use the lib with a RAM disk, so that nothing the library does touches actual disk plates in any way. The idea is to make the tests very fast to run and clean up (drop RAM disk?).

The two most prominent options available to me are Commons VFS and JSR 203. The former is of no use to me because I want things to work transparently using the java.io.* API and not Commons VFS classes. The later doesn't cut it because I have to make do with JDK 6 (it's supposed to be a part of JDK 7) and I don't know if it will work seamlessly with java.io.* anyway (I wouldn't bet on it).

There are other solutions as well, but I can't use them for the same reason I can't use Commons VFS. Mocks are out of the question because of the complexity of the library in question.

On my linux machine, I can easily create a RAM drive and use the java.io.* API the same way I would with files on disk. The thing is, I want it to be cross-platform and more specifically, to make disk setup a part of the test procedure, rather than something external.

So, is there a way to register a RAM drive in Java which would be usable with the standard java.io.* API?

Was it helpful?

Solution

So, is there a way to register a RAM drive in Java which would be usable with the standard java.io.* API?

Not with a Java 6 or earlier JVM. Java 6 and earlier do not provide any SPI for registering file systems or file system types. So, to implement a RAM FS that an application would use like a normal FS would entail modifying the behavior of a number of java.io.* classes.

I think that the best thing that you could do would be to use a RAM FS implemented by the host operating system. You should be able to access that from Java as if it was a normal file system. However, I/O would entail a system calls, so it wouldn't be as fast as if the RAM file system was held in the JVM managed memory.

OTHER TIPS

Theoretically Stephen is right. But I can suggest you a trick. You can implement your own FileInputStream and FileOutputStream and put them into bootclasspath. Your implementation will for example implement open(), read() and readBytes() (that are native method in regular FileInputStream.)

This is pure java solution for your problem. Its disadvantage is that you have to run your tests in separate instance of JVM.

The basic problem that you are seeking to overcome is that the original java.io APIs are not flexible at all (they all refer to concrete classes). The only way that you can put different functionality in, for example java.io.File, is by extending the base class.

Extending classes after they are designed can be bad design (Just look at the Properties class) - which is why you probably won't find a library that does that.

Nothing prevents you from extending the java.io.File class yourself, and proxy all the methods to, for example, a FileObject of the Commons VFS API.

Edit: However, there are things that will probably fail under that approach - for example, using the File constructors that take a parent File.

Edit 2: Well, I would begin with something like that:

public class VirtualFile extends java.io.File {
    public static VirtualFile fromFile(File file) {
        if (file instanceof VirtualFile) {
            return (VirtualFile) file;
        } else {
            FileSystemManager fsm = new DefaultFileSystemManager();
            return fsm.toFileObject(file);
        }
    }

    private final org.apache.commons.vfs.FileObject mProxyFileObject;


    public VirtualFile(FileObject proxy) {
        super("/tmp/xxxx"); // That part needs some work to be cross-platform.
                            // However, such a construction will completely
                            // destroy the expectations that other classes 
                            // have about what a File is.
        mProxyFileObject = proxy;
    }

    public VirtualFile(VirtualFile parent, String child) {
        this(parent.mProxyFileObject.resolveFile(child));
    }

    public VirtualFile(File parent, String child) {
        this(fromFile(parent), child);
    }

    @Override
    public boolean canExecute() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean canRead() {
        try {
            return mProxyFileObject.isReadable();
        } catch (FileSystemException fse) {
            // FileSystemException is not a Runtime Exception :(
            throw new RuntimeException(fse);
        }
    }

    // Override ALL public methods to throw Exceptions; 
    // implement or mock only the methods that you need.
}

As for why the File(File, String) constructor would not work with that setup: that constructor does not expect an implementation of File to break the class's contract - which we do when we call super("/tmp/xxxx"). (And we can't avoid breaking the class's contract, because the virtual files that we want to work with do not have a plain File equivalent)

So, there you are - it would require a nontrivial bit of work, and there is a significant chance that the library would not work as expected anyway.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top