org.apache.hadoop.io.TestBufferedByteInputOutput.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.io.TestBufferedByteInputOutput.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.InjectionEventCore;
import org.apache.hadoop.util.InjectionEventI;
import org.apache.hadoop.util.InjectionHandler;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Test;

public class TestBufferedByteInputOutput {

    static final Log LOG = LogFactory.getLog(TestBufferedByteInputOutput.class.getName());

    private byte[] input;
    private byte[] output;

    private Random rand = new Random();

    private void setUp(int inputSize) {
        input = new byte[inputSize];
        output = new byte[inputSize];
        rand.nextBytes(input);
    }

    @After
    public void tearDown() {
        InjectionHandler.clear();
    }

    /**
     * Test raw buffer writes and reads.
     */
    @Test
    public void testBuffer() throws Exception {
        for (int i = 0; i < 100; i++) {
            testBuffer(rand(1000), rand(1000), -1, true);
        }
    }

    /**
     * Test basic functionality
     */
    @Test
    public void testBufferBasic() throws Exception {

        setUp(50);
        final BufferedByteInputOutput buffer = new BufferedByteInputOutput(50);

        // empty buffer
        assertEquals(0, buffer.available());
        assertEquals(0, buffer.totalRead());
        assertEquals(0, buffer.totalWritten());

        // write 20 bytes
        buffer.write(input, 0, 20);
        assertEquals(20, buffer.available());
        assertEquals(0, buffer.totalRead());
        assertEquals(20, buffer.totalWritten());

        // read 10 bytes
        buffer.read(output, 0, 10);
        assertEquals(10, buffer.available());
        assertEquals(10, buffer.totalRead());
        assertEquals(20, buffer.totalWritten());

        // no more writes should be accepted
        // we still should be able to read 10 bytes
        buffer.close();
        try {
            buffer.write(1);
            fail("Should not accept writes");
        } catch (Exception e) {
            LOG.info("Expected exception");
        }
        // try to read 20 bytes
        assertEquals(10, buffer.available());
        assertEquals(10, buffer.read(output, 10, 20));

        // next read should return -1
        assertEquals(0, buffer.available());
        assertEquals(-1, buffer.read(output, 20, 5));
        assertEquals(-1, buffer.read());
    }

    /**
     * Can be used for testing throughput on large buffer.
     */
    @Test
    public void testBufferThroughput() throws Exception {
        LOG.info("Test buffer throughput");
        long time = testBuffer(1024 * 1024, 10 * 1024, 1024, false);
        LOG.info("Time taken: " + time);
    }

    /**
     * Test pipeline where background thread reads from underlying stream.
     */
    @Test
    public void testInputStream() throws IOException {
        for (int i = 0; i < 100; i++) {
            testInputStream(rand(1000), rand(1000), rand(1000));
        }
    }

    /**
     * Test pipeline where background thread writes to underlying stream.
     */
    @Test
    public void testOutputStream() throws IOException {
        // do close after completing writes
        for (int i = 0; i < 100; i++) {
            testOutputStream(rand(1000), rand(1000), rand(1000), true);
        }

        // do flush after completing writes
        for (int i = 0; i < 100; i++) {
            testOutputStream(rand(1000), rand(1000), rand(1000), false);
        }
    }

    /**
     * Test reading from closed buffer.
     */
    @Test
    public void testCloseInput() throws IOException {
        LOG.info("Running test close input");
        setUp(1000);

        // input is of size 1000, so the ReadThread will attempt to write to
        // the buffer, which will fail, but we should be able to read 100 bytes
        ByteArrayInputStream is = new ByteArrayInputStream(input);
        DataInputStream dis = BufferedByteInputStream.wrapInputStream(is, 100, 10);

        // wait for the thread to read from is and
        // write to the buffer
        while (dis.available() < 100) {
            sleep(10);
        }

        // no more writes to the internal buffer
        dis.close();

        try {
            dis.read(); // read will call DataInputStream fill() which should fail
            fail("Read should fail because we are closed");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        dis.close(); // can call multiple close()  

        try {
            dis.read(new byte[10], 0, 10);
            fail("Read should fail because we are closed");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        try {
            dis.available();
            fail("Available should fail because we are closed");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }
    }

    /**
     * Test if writing to closed buffer fails.
     */
    @Test
    public void testCloseOutput() throws IOException {
        LOG.info("Running test close output");
        ByteArrayOutputStream os = new ByteArrayOutputStream(1000);
        DataOutputStream dos = BufferedByteOutputStream.wrapOutputStream(os, 100, 10);

        dos.close();
        dos.close(); // can close multiple times

        try {
            // this will cause to flush BufferedOutputStream
            for (int i = 0; i < 10000; i++) {
                dos.write(1);
            }
            fail("Write should fail");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        try {
            // this will cause to flush BufferedOutputStream
            for (int i = 0; i < 1000; i++) {
                dos.write(new byte[10], 0, 10);
            }
            fail("Write should fail");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        try {
            dos.flush();
            fail("Flush should fail");
        } catch (Exception e) {
            LOG.info("Expected exception " + e.getMessage());
        }
    }

    private long testBuffer(final int inputSize, final int bufferSize, final int fixedTempBufferSize,
            final boolean writeOutput) throws Exception {
        LOG.info("Running test raw buffer with input size: " + inputSize + ", buffer size: " + bufferSize);
        setUp(inputSize);
        final BufferedByteInputOutput buffer = new BufferedByteInputOutput(bufferSize);

        Callable<Void> writerThread = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                int totalWritten = 0, inputCursor = 0;
                while (totalWritten < inputSize) {
                    if (rand.nextBoolean()) {
                        // write single byte
                        buffer.write(input[inputCursor++]);
                        totalWritten++;
                    } else {
                        int count;
                        if (fixedTempBufferSize > 0) {
                            count = Math.min(inputSize - totalWritten, rand.nextInt(fixedTempBufferSize) + 1);
                        } else {
                            count = rand.nextInt(inputSize - totalWritten) + 1;
                        }
                        buffer.write(input, inputCursor, count);
                        inputCursor += count;
                        totalWritten += count;
                    }
                }
                buffer.close();
                return null;
            }
        };
        Callable<Void> readerThread = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                int totalRead = 0, outputCursor = 0;
                while (totalRead < inputSize) {
                    if (rand.nextBoolean()) {
                        // read single byte
                        int b = buffer.read();
                        assertFalse(b == -1);
                        output[outputCursor++] = (byte) b;
                        totalRead++;
                    } else {
                        int count;
                        if (fixedTempBufferSize > 0) {
                            count = rand.nextInt(fixedTempBufferSize) + 1;
                        } else {
                            count = rand.nextInt(inputSize - totalRead) + 1;
                        }
                        byte[] bytes = new byte[count];
                        int bytesRead = buffer.read(bytes, 0, count);
                        assertFalse(bytesRead == -1);
                        if (writeOutput) {
                            System.arraycopy(bytes, 0, output, outputCursor, bytesRead);
                            outputCursor += bytesRead;
                        }
                        totalRead += bytesRead;
                    }
                }
                return null;
            }
        };

        assertEquals(0, buffer.available());

        //////////////// run writer and reader
        long start = System.currentTimeMillis();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Void> readerFuture = executor.submit(readerThread);
        Future<Void> writerFuture = executor.submit(writerThread);
        readerFuture.get();
        writerFuture.get();
        long stop = System.currentTimeMillis();
        ////////////////

        if (writeOutput) {
            assertTrue(Arrays.equals(input, output));
        }

        // check written and read bytes
        assertEquals(inputSize, buffer.totalRead());
        assertEquals(inputSize, buffer.totalWritten());

        buffer.close();

        // should get -1 after closing for reads 
        // since everything has been read
        assertEquals(-1, buffer.read());
        assertEquals(-1, buffer.read(new byte[10], 0, 10));

        // should fail writes
        try {
            buffer.write(1);
            fail("Should get exception after closing");
        } catch (IOException e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        try {
            buffer.write(new byte[10], 0, 10);
            fail("Should get exception after closing");
        } catch (IOException e) {
            LOG.info("Expected exception " + e.getMessage());
        }

        assertTrue(buffer.isClosed());
        return stop - start;
    }

    private void testInputStream(int inputSize, int bufferSize, int readBufferSize) throws IOException {
        LOG.info("Running test input stream with inputSize: " + inputSize + ", bufferSize: " + bufferSize
                + ", readBufferSize: " + readBufferSize);
        setUp(inputSize);
        ByteArrayInputStream is = new ByteArrayInputStream(input);
        DataInputStream dis = BufferedByteInputStream.wrapInputStream(is, bufferSize, readBufferSize);

        int totalRead = 0;
        int outputCursor = 0;
        while (totalRead < inputSize) {
            if (rand.nextBoolean()) {
                // read single byte
                output[outputCursor++] = dis.readByte();
                totalRead++;
            } else {
                int count = rand.nextInt(inputSize - totalRead) + 1;
                byte[] bytes = new byte[count];
                int bytesRead = dis.read(bytes, 0, count);
                System.arraycopy(bytes, 0, output, outputCursor, bytesRead);
                outputCursor += bytesRead;
                totalRead += bytesRead;
            }
        }

        assertEquals(inputSize, totalRead);
        assertTrue(Arrays.equals(input, output));

        dis.close();
        dis.close(); // multiple close should work
        dis.close(); // multiple close should work
    }

    private void testOutputStream(int inputSize, int bufferSize, int writeBufferSize, boolean closeAndCheck)
            throws IOException {
        TestBufferedByteInputOutputInjectionHandler h = new TestBufferedByteInputOutputInjectionHandler();
        InjectionHandler.set(h);
        LOG.info("Running test output stream with inputSize: " + inputSize + ", bufferSize: " + bufferSize
                + ", readBufferSize: " + writeBufferSize);
        setUp(inputSize);
        ByteArrayOutputStream os = new ByteArrayOutputStream(inputSize);
        DataOutputStream dos = BufferedByteOutputStream.wrapOutputStream(os, bufferSize, writeBufferSize);

        int totalWritten = 0;
        int inputCursor = 0;
        while (totalWritten < inputSize) {
            if (rand.nextBoolean()) {
                // write single byte
                dos.write(input[inputCursor++]);
                totalWritten++;
            } else {
                int count = rand.nextInt(inputSize - totalWritten) + 1;
                dos.write(input, inputCursor, count);
                inputCursor += count;
                totalWritten += count;
            }

            // random flush
            if (rand.nextBoolean()) {
                h.bytesFlushed = -1;
                dos.flush();
                assertEquals(totalWritten, h.bytesFlushed);
            }
        }

        if (closeAndCheck) {
            // we either close
            dos.close();
        } else {
            // or need to flush the stream
            dos.flush();
        } // in either case data must be out

        assertEquals(inputSize, totalWritten);
        assertTrue(Arrays.equals(input, os.toByteArray()));

        // close
        dos.close();
        dos.close(); // should be fine to call it multiple times
        dos.close();
    }

    private int rand(int n) {
        return rand.nextInt(n) + 1;
    }

    private void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            LOG.info("Interrupted exception", e);
            Thread.currentThread().interrupt();
        }
    }

    class TestBufferedByteInputOutputInjectionHandler extends InjectionHandler {
        volatile long bytesFlushed;

        @Override
        protected void _processEvent(InjectionEventI event, Object... args) {
            if (event == InjectionEventCore.BUFFEREDBYTEOUTPUTSTREAM_FLUSH) {
                bytesFlushed = (Long) args[0];
            }
        }
    }
}