简体   繁体   中英

Always blocking input stream for testing?

I'm doing some unit tests where essentially I need the input stream to block forever. Right now I'm using this to construct the input stream

InputStream in = new ByteArrayInputStream("".getBytes());

While it works some of the time, other times the input stream is read before the output stream (what I'm testing) is finished, causing all sorts of havoc.

Essentially I need this input stream to block forever when read. The only solution I can think of is to setup the InputStream with a massive buffer so that the other threads finish, but thats a really hackish and brittle solution. I do have mockito but I'm very new at it and not sure if I can get away with only mocking read without mocking anything else.

Does anyone know of a better solution?


EDIT:

This is my new attempt. It works most of the time, but other times the input thread dies early which causes the Output Thread to die (that behavior is intentional). I can't seem to figure out though why this would sometimes fail.

This is the general test under TestNG simplified for clarity.

    protected CountDownLatch inputLatch;

    @BeforeMethod
    public void botSetup() throws Exception {
            //Setup streams for bot
            PipedOutputStream out = new PipedOutputStream();
            //Create an input stream that we'll kill later
            inputLatch = new CountDownLatch(1);
            in = new AutoCloseInputStream(new ByteArrayInputStream("".getBytes()) {
                    @Override
                    public synchronized int read() {
                            try {
                                    //Block until were killed
                                    inputLatch.await();
                            } catch (InterruptedException ex) {
                                    //Wrap in an RuntimeException so whatever was using this fails
                                    throw new RuntimeException("Interrupted while waiting for input", ex);
                            }
                            //No more input
                            return -1;
                    }
            });
            Socket socket = mock(Socket.class);
            when(socket.getInputStream()).thenReturn(in);
            when(socket.getOutputStream()).thenReturn(out);

            //Setup ability to read from bots output
            botOut = new BufferedReader(new InputStreamReader(new PipedInputStream(out)));
            ...
    }

    @AfterMethod
    public void cleanUp() {
            inputLatch.countDown();
            bot.dispose();
    }

For the test I use readLine() from botOut to get the appropriate number of lines. The issue though is that when the output thread dies, readLine() blocks forever which hangs up TestNG. I've tried a timeout with mixed results: most of the time it would work but others it would kill tests that just took a little longer than normal to test.

My only other option is to just not use streams for this kind of work. The output thread relies on an output queue, so I could just run off of that. The issue though is that I'm not actually testing writing to the stream, just what is going to be sent, which does bother me.

I'd make an InputStream that, when read(), does a wait() on something that's held locked till you're done with the rest of the test. Subclass from FilterInputStream to get everything else for free.

Mockito is great- I am personally a huge fan!

With Mockito, you can do something like the code below. You basically set up a stream mock, and you tell it to sleep for a very long time when the "read" method is invoked on it. You can then pass this mock into the code you want to test when the stream hangs.

import static org.mockito.Mockito.*;

//...
@Test
public void testMockitoSleepOnInputStreamRead() throws Exception{

    InputStream is = mock(InputStream.class);
    when(is.read()).thenAnswer(new Answer() {
        @Override
        public Object answer(InvocationOnMock invocation) {
            try {
            Thread.sleep(10000000000L);
            return null;
            } catch (InterruptedException ie) {
                throw new RuntimeException(ie);
            }

        }
    });

    //then use this input stream for your testing.
}

There doesn't seem to be any reliable way to do this. My code in the question only works sometimes, @Moe's doesn't work at all, @Ed's suggesting is what I was origionally doing, and @SJuan's is sort of what I'm already doing.

There just seems to be too much stuff going on. The input stream I give to the class is wrapped in a InputStreamReader, then a Buffered reader. Suggestions for other streams inside of other streams just further complicate the issue.

To fix the problem I did what I should of done origionally: Create a factory method for the InputThread (the thread that actually does the reading), then override in my testing. Simple, effective, and 100% reliable.

I suggest anyone that runs into this problem to first try and override the part of your program that does the reading. If you can't then the code I posted is the only semi-reliable code that works in my situation.

I created a helper class that extends ByteArrayInputStream for my unit tests. It pipes a given byte[] through, but at the end of the stream instead of returning -1 it waits until close() is called. If ten seconds passes it gives up and throws an exception.

If you want it to close earlier you can call latch.countdown() yourself.

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class BlockingByteArrayInputStream extends ByteArrayInputStream {
    private CountDownLatch latch;

    public BlockingByteArrayInputStream(byte[] buf) {
        super(buf);
        latch = new CountDownLatch(1);
    }

    @Override
    public synchronized int read() {
        int read = super.read();
        if (read == -1) {
            waitForUnblock();
        }
        return read;
    }

    @Override
    public int read(byte[] b) throws IOException {
        int read = super.read(b);
        if (read == -1) {
            waitForUnblock();
        }
        return read;
    }

    @Override
    public synchronized int read(byte[] b, int off, int len) {
        int read = super.read(b, off, len);
        if (read == -1) {
            waitForUnblock();
        }
        return read;
    }

    private void waitForUnblock() {
        try {
            latch.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException("safeAwait interrupted");
        }
    }

    @Override
    public void close() throws IOException {
        super.close();
        latch.countDown();
    }
}

Then you need another InputStream flavour. Read block when no more bytes are available, but with ByteArrayOutputStream they are always available until the end of stream is found.

I would extend BAOS by changing read() so it checks for a certain boolean value (if true read, if false wait a second and loop). Then change that variable from your unit code when it is the right time.

Hope that helps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM