14

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?

Tomislav Nakic-Alfirevic
  • 10,017
  • 5
  • 38
  • 51
  • 3
    Why no look outside of Java. Create a RAM disk at the OS level (tools exist for WIndows and Linux) and just give your library that as the path. –  Dec 13 '10 at 11:25
  • 8
    I already explained why: I ideally wanted the procedure to be platform-independent. Additionally, I would prefer any necessary code to be in Java, rather than writing external scripts to set up and dispose a RAM disk in one environment or another. – Tomislav Nakic-Alfirevic Dec 13 '10 at 12:09
  • Have you found a solution? For Java 7 or Java 8. – Jus12 Dec 02 '16 at 18:07
  • @Jus12 Sorry, I haven't heard of a solution in the meantime. – Tomislav Nakic-Alfirevic Dec 03 '16 at 18:54

3 Answers3

6

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
5

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.

AlexR
  • 114,158
  • 16
  • 130
  • 208
  • 2
    It's not really "pure Java" insofar as modifying system classes is not in the scope of the Java specs, i.e. this might work on some Java implementations, but won't work on others. – Joachim Sauer Dec 13 '10 at 12:03
  • 2
    my understanding is that he can't do that, even if he wants to. He says *"[t]he later doesn't cut it because I have to make do with JDK 6"*. If he replaces `java.io.*` classes with custom versions then it is not Java 6 anymore. (Indeed, technically it is not Java at all!) – Stephen C Dec 13 '10 at 12:06
  • 2
    besides, replacing standard java classes is definitely NOT a "pure Java"; see this link - http://www.javacoffeebreak.com/faq/faq0006.html . Depending on a hacked JVM automatically renders your code non-portable, and therefore not "pure Java", IMO. – Stephen C Dec 13 '10 at 12:10
  • 1
    Thank you for the suggestion, Alex. Implementing these classes to work using a RAM drive and managing different versions of JVMs seems quite a bit of work and has a hackish feel to it. From the articles I read, my use case is quite a frequent one so I had hoped for something more straightforward, all the more so because I cannot allocate too much time to an optimization problem. :\ It might very well be a valid approach to get the job done, nevertheless: I will leave it for someone with a looser schedule to investigate. – Tomislav Nakic-Alfirevic Dec 13 '10 at 12:21
  • @Stephen: modifying a selected small set of JDK 6 classes to be able to run unit tests (i.e. not to use in production) would be more acceptable to me than using e.g. JDK 7 which is different across the board. The problem for me is that it seems to require days rather than hours of work, which I cannot afford right now. – Tomislav Nakic-Alfirevic Dec 13 '10 at 12:26
3

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.

Jean Hominal
  • 16,518
  • 5
  • 56
  • 90
  • Could you elaborate a bit on how you would proxy calls from java.io.File to e.g. a FileObject and what the problem with the File(File) constructor would be? – Tomislav Nakic-Alfirevic Dec 13 '10 at 12:36
  • Thank you for a rather extensive clarification, Jean. Unfortunately, I fail to see how it improves the situation compared to simply using Commons VFS. The 3rd party library I'm using doesn't know of a VirtualFile class and would (of course) not use it. If it were written to use an abstraction layer like the one you described, that might have given me something to work with. It seems I'm going to be stuck with files on disk, for the time being. – Tomislav Nakic-Alfirevic Dec 13 '10 at 15:33
  • @Tomislav: I didn't know how much control you had on the library - or maybe you would be able to provide a basic `File` to the library. – Jean Hominal Dec 13 '10 at 16:02