IOUtilTest.java

package org.codehaus.plexus.util;

/*
 * Copyright The Codehaus Foundation.
 *
 * Licensed 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.
 */

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * This is used to test IOUtil for correctness. The following checks are performed:
 * <ul>
 * <li>The return must not be null, must be the same type and equals() to the method's second arg</li>
 * <li>All bytes must have been read from the source (available() == 0)</li>
 * <li>The source and destination content must be identical (byte-wise comparison check)</li>
 * <li>The output stream must not have been closed (a byte/char is written to test this, and subsequent size
 * checked)</li>
 * </ul>
 * Due to interdependencies in IOUtils and IOUtilsTestlet, one bug may cause multiple tests to fail.
 *
 * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
 * @version $Id: $Id
 * @since 3.4.0
 */
public final class IOUtilTest {
    /*
     * Note: this is not particularly beautiful code. A better way to check for flush and close status would be to
     * implement "trojan horse" wrapper implementations of the various stream classes, which set a flag when relevant
     * methods are called. (JT)
     */

    private final int FILE_SIZE = 1024 * 4 + 1;

    private File testDirectory;

    private File testFile;

    /**
     * <p>setUp.</p>
     */
    @BeforeEach
    void setUp() {
        try {
            testDirectory = (new File("target/test/io/")).getAbsoluteFile();
            if (!testDirectory.exists()) {
                testDirectory.mkdirs();
            }

            testFile = new File(testDirectory, "file2-test.txt");

            createFile(testFile, FILE_SIZE);
        } catch (IOException ioe) {
            throw new RuntimeException("Can't run this test because environment could not be built");
        }
    }

    /**
     * <p>tearDown.</p>
     */
    public void tearDown() {
        testFile.delete();
        testDirectory.delete();
    }

    private void createFile(File file, long size) throws IOException {
        BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()));

        for (int i = 0; i < size; i++) {
            output.write((byte) (i % 128)); // nice varied byte pattern compatible with Readers and Writers
        }

        output.close();
    }

    /** Assert that the contents of two byte arrays are the same. */
    private void assertEqualContent(byte[] b0, byte[] b1) {
        assertArrayEquals(b0, b1, "Content not equal according to java.util.Arrays#equals()");
    }

    /** Assert that the content of two files is the same. */
    private void assertEqualContent(File f0, File f1) throws IOException {
        byte[] buf0 = Files.readAllBytes(f0.toPath());
        byte[] buf1 = Files.readAllBytes(f1.toPath());
        assertArrayEquals(buf0, buf1, "The files " + f0 + " and " + f1 + " have different content");
    }

    /** Assert that the content of a file is equal to that in a byte[]. */
    private void assertEqualContent(byte[] b0, File file) throws IOException {
        byte[] b1 = Files.readAllBytes(file.toPath());
        assertArrayEquals(b0, b1, "Content differs");
    }

    /**
     * <p>testInputStreamToOutputStream.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void inputStreamToOutputStream() throws Exception {
        File destination = newFile("copy1.txt");
        InputStream fin = Files.newInputStream(testFile.toPath());
        OutputStream fout = Files.newOutputStream(destination.toPath());

        IOUtil.copy(fin, fout);
        assertEquals(0, fin.available(), "Not all bytes were read");
        fout.flush();

        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testInputStreamToWriter.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void inputStreamToWriter() throws Exception {
        File destination = newFile("copy2.txt");
        InputStream fin = Files.newInputStream(testFile.toPath());
        Writer fout = Files.newBufferedWriter(destination.toPath());

        IOUtil.copy(fin, fout);

        assertEquals(0, fin.available(), "Not all bytes were read");
        fout.flush();

        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testInputStreamToString.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void inputStreamToString() throws Exception {
        InputStream fin = Files.newInputStream(testFile.toPath());
        String out = IOUtil.toString(fin);
        assertNotNull(out);
        assertEquals(0, fin.available(), "Not all bytes were read");
        assertEquals(out.length(), FILE_SIZE, "Wrong output size: out.length()=" + out.length() + "!=" + FILE_SIZE);
        fin.close();
    }

    /**
     * <p>testReaderToOutputStream.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void readerToOutputStream() throws Exception {
        File destination = newFile("copy3.txt");
        Reader fin = Files.newBufferedReader(testFile.toPath());
        OutputStream fout = Files.newOutputStream(destination.toPath());
        IOUtil.copy(fin, fout);
        // Note: this method *does* flush. It is equivalent to:
        // OutputStreamWriter _out = new OutputStreamWriter(fout);
        // IOUtil.copy( fin, _out, 4096 ); // copy( Reader, Writer, int );
        // _out.flush();
        // out = fout;

        // Note: rely on the method to flush
        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testReaderToWriter.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void readerToWriter() throws Exception {
        File destination = newFile("copy4.txt");
        Reader fin = Files.newBufferedReader(testFile.toPath());
        Writer fout = Files.newBufferedWriter(destination.toPath());
        IOUtil.copy(fin, fout);

        fout.flush();
        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testReaderToString.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void readerToString() throws Exception {
        Reader fin = Files.newBufferedReader(testFile.toPath());
        String out = IOUtil.toString(fin);
        assertNotNull(out);
        assertEquals(out.length(), FILE_SIZE, "Wrong output size: out.length()=" + out.length() + "!=" + FILE_SIZE);
        fin.close();
    }

    /**
     * <p>testStringToOutputStream.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void stringToOutputStream() throws Exception {
        File destination = newFile("copy5.txt");
        Reader fin = Files.newBufferedReader(testFile.toPath());
        // Create our String. Rely on testReaderToString() to make sure this is valid.
        String str = IOUtil.toString(fin);
        OutputStream fout = Files.newOutputStream(destination.toPath());
        IOUtil.copy(str, fout);
        // Note: this method *does* flush. It is equivalent to:
        // OutputStreamWriter _out = new OutputStreamWriter(fout);
        // IOUtil.copy( str, _out, 4096 ); // copy( Reader, Writer, int );
        // _out.flush();
        // out = fout;
        // note: we don't flush here; this IOUtils method does it for us

        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testStringToWriter.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void stringToWriter() throws Exception {
        File destination = newFile("copy6.txt");
        Reader fin = Files.newBufferedReader(testFile.toPath());
        // Create our String. Rely on testReaderToString() to make sure this is valid.
        String str = IOUtil.toString(fin);
        Writer fout = Files.newBufferedWriter(destination.toPath());
        IOUtil.copy(str, fout);
        fout.flush();

        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();

        deleteFile(destination);
    }

    /**
     * <p>testInputStreamToByteArray.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void inputStreamToByteArray() throws Exception {
        InputStream fin = Files.newInputStream(testFile.toPath());
        byte[] out = IOUtil.toByteArray(fin);
        assertNotNull(out);
        assertEquals(0, fin.available(), "Not all bytes were read");
        assertEquals(out.length, FILE_SIZE, "Wrong output size: out.length=" + out.length + "!=" + FILE_SIZE);
        assertEqualContent(out, testFile);
        fin.close();
    }

    /**
     * <p>testStringToByteArray.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void stringToByteArray() throws Exception {
        Reader fin = Files.newBufferedReader(testFile.toPath());

        // Create our String. Rely on testReaderToString() to make sure this is valid.
        String str = IOUtil.toString(fin);

        byte[] out = IOUtil.toByteArray(str);
        assertEqualContent(str.getBytes(), out);
        fin.close();
    }

    /**
     * <p>testByteArrayToWriter.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void byteArrayToWriter() throws Exception {
        File destination = newFile("copy7.txt");
        Writer fout = Files.newBufferedWriter(destination.toPath());
        InputStream fin = Files.newInputStream(testFile.toPath());

        // Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid.
        byte[] in = IOUtil.toByteArray(fin);
        IOUtil.copy(in, fout);
        fout.flush();
        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    /**
     * <p>testByteArrayToString.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void byteArrayToString() throws Exception {
        InputStream fin = Files.newInputStream(testFile.toPath());
        byte[] in = IOUtil.toByteArray(fin);
        // Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid.
        String str = IOUtil.toString(in);
        assertEqualContent(in, str.getBytes());
        fin.close();
    }

    /**
     * <p>testByteArrayToOutputStream.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void byteArrayToOutputStream() throws Exception {
        File destination = newFile("copy8.txt");
        OutputStream fout = Files.newOutputStream(destination.toPath());
        InputStream fin = Files.newInputStream(testFile.toPath());

        // Create our byte[]. Rely on testInputStreamToByteArray() to make sure this is valid.
        byte[] in = IOUtil.toByteArray(fin);

        IOUtil.copy(in, fout);

        fout.flush();

        checkFile(destination);
        checkWrite(fout);
        fout.close();
        fin.close();
        deleteFile(destination);
    }

    // ----------------------------------------------------------------------
    // Test closeXXX()
    // ----------------------------------------------------------------------

    /**
     * <p>testCloseInputStream.</p>
     *
     */
    @Test
    void closeInputStream() {
        IOUtil.close((InputStream) null);

        TestInputStream inputStream = new TestInputStream();

        IOUtil.close(inputStream);

        assertTrue(inputStream.closed);
    }

    /**
     * <p>testCloseOutputStream.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void closeOutputStream() throws Exception {
        IOUtil.close((OutputStream) null);

        TestOutputStream outputStream = new TestOutputStream();

        IOUtil.close(outputStream);

        assertTrue(outputStream.closed);
    }

    /**
     * <p>testCloseReader.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void closeReader() throws Exception {
        IOUtil.close((Reader) null);

        TestReader reader = new TestReader();

        IOUtil.close(reader);

        assertTrue(reader.closed);
    }

    /**
     * <p>testCloseWriter.</p>
     *
     * @throws java.lang.Exception if any.
     */
    @Test
    void closeWriter() throws Exception {
        IOUtil.close((Writer) null);

        TestWriter writer = new TestWriter();

        IOUtil.close(writer);

        assertTrue(writer.closed);
    }

    private class TestInputStream extends InputStream {
        boolean closed;

        public void close() {
            closed = true;
        }

        public int read() {
            fail("This method shouldn't be called");

            return 0;
        }
    }

    private class TestOutputStream extends OutputStream {
        boolean closed;

        public void close() {
            closed = true;
        }

        public void write(int value) {
            fail("This method shouldn't be called");
        }
    }

    private class TestReader extends Reader {
        boolean closed;

        public void close() {
            closed = true;
        }

        public int read(char[] cbuf, int off, int len) {
            fail("This method shouldn't be called");

            return 0;
        }
    }

    private static class TestWriter extends Writer {
        boolean closed;

        public void close() {
            closed = true;
        }

        public void write(char[] cbuf, int off, int len) {
            fail("This method shouldn't be called");
        }

        public void flush() {
            fail("This method shouldn't be called");
        }
    }

    // ----------------------------------------------------------------------
    // Utility methods
    // ----------------------------------------------------------------------

    private File newFile(String filename) throws Exception {
        File destination = new File(testDirectory, filename);
        assertFalse(destination.exists(), filename + "Test output data file shouldn't previously exist");

        return destination;
    }

    private void checkFile(File file) throws Exception {
        assertTrue(file.exists(), "Check existence of output file");
        assertEqualContent(testFile, file);
    }

    private void checkWrite(OutputStream output) throws Exception {
        try {
            new PrintStream(output).write(0);
        } catch (Throwable t) {
            throw new Exception("The copy() method closed the stream " + "when it shouldn't have. " + t.getMessage());
        }
    }

    private void checkWrite(Writer output) throws Exception {
        try {
            new PrintWriter(output).write('a');
        } catch (Throwable t) {
            throw new Exception("The copy() method closed the stream " + "when it shouldn't have. " + t.getMessage());
        }
    }

    private void deleteFile(File file) throws Exception {
        assertEquals(
                file.length(),
                FILE_SIZE + 1,
                "Wrong output size: file.length()=" + file.length() + "!=" + FILE_SIZE + 1);

        assertTrue((file.delete() || (!file.exists())), "File would not delete");
    }
}