Bug 7521: Add FileBackedOutputStream and use for snapshot chunking
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / test / java / org / opendaylight / controller / cluster / io / FileBackedOutputStreamTest.java
diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/io/FileBackedOutputStreamTest.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/io/FileBackedOutputStreamTest.java
new file mode 100644 (file)
index 0000000..bcb1850
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2017 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.io;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unit tests for FileBackedOutputStream.
+ *
+ * @author Thomas Pantelis
+ */
+public class FileBackedOutputStreamTest {
+    private static final Logger LOG = LoggerFactory.getLogger(FileBackedOutputStreamTest.class);
+    private static final String TEMP_DIR = "target/FileBackedOutputStreamTest";
+
+    @BeforeClass
+    public static void staticSetup() {
+        File dir = new File(TEMP_DIR);
+        if (!dir.exists() && !dir.mkdirs()) {
+            throw new RuntimeException("Failed to create temp dir " + TEMP_DIR);
+        }
+    }
+
+    @AfterClass
+    public static void staticCleanup() {
+        deleteTempFiles();
+        deleteFile(TEMP_DIR);
+    }
+
+    @Before
+    public void setup() {
+        deleteTempFiles();
+        FileBackedOutputStream.REFERENCE_CACHE.clear();
+    }
+
+    @After
+    public void cleanup() {
+        deleteTempFiles();
+    }
+
+    @Test
+    public void testFileThresholdNotReached() throws IOException {
+        LOG.info("testFileThresholdNotReached starting");
+        try (FileBackedOutputStream fbos = new FileBackedOutputStream(10, TEMP_DIR)) {
+            byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
+            fbos.write(bytes[0]);
+            fbos.write(bytes, 1, bytes.length - 1);
+
+            assertEquals("getCount", bytes.length, fbos.getCount());
+            assertNull("Found unexpected temp file", findTempFileName());
+            assertEquals("Size", bytes.length, fbos.asByteSource().size());
+
+            // Read bytes twice.
+            assertArrayEquals("Read bytes", bytes, fbos.asByteSource().read());
+            assertArrayEquals("Read bytes", bytes, fbos.asByteSource().read());
+
+            assertEquals("Reference cache size", 0, FileBackedOutputStream.REFERENCE_CACHE.size());
+
+            fbos.cleanup();
+        }
+
+        LOG.info("testFileThresholdNotReached ending");
+    }
+
+    @Test
+    public void testFileThresholdReachedWithWriteBytes() throws IOException {
+        LOG.info("testFileThresholdReachedWithWriteBytes starting");
+        try (FileBackedOutputStream fbos = new FileBackedOutputStream(10, TEMP_DIR)) {
+            byte[] bytes = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+            fbos.write(bytes[0]);
+            fbos.write(bytes, 1, 11);
+
+            String tempFileName = findTempFileName();
+            assertNotNull("Expected temp file created", tempFileName);
+
+            fbos.write(bytes[12]);
+            fbos.write(bytes, 13, bytes.length - 13);
+
+            assertEquals("Temp file", tempFileName, findTempFileName());
+            assertEquals("Size", bytes.length, fbos.asByteSource().size());
+
+            InputStream inputStream = fbos.asByteSource().openStream();
+
+            assertArrayEquals("Read bytes", bytes, fbos.asByteSource().read());
+
+            byte[] inBytes = new byte[bytes.length];
+            assertEquals("# bytes read", bytes.length, inputStream.read(inBytes));
+            assertArrayEquals("Read InputStream", bytes, inBytes);
+            assertEquals("End of stream", -1, inputStream.read());
+
+            inputStream.close();
+
+            assertEquals("Reference cache size", 1, FileBackedOutputStream.REFERENCE_CACHE.size());
+
+            fbos.cleanup();
+
+            assertEquals("Reference cache size", 0, FileBackedOutputStream.REFERENCE_CACHE.size());
+
+            assertNull("Found unexpected temp file", findTempFileName());
+        }
+
+        LOG.info("testFileThresholdReachedWithWriteBytes ending");
+    }
+
+    @Test
+    public void testFileThresholdReachedWithWriteByte() throws IOException {
+        LOG.info("testFileThresholdReachedWithWriteByte starting");
+        try (FileBackedOutputStream fbos = new FileBackedOutputStream(2, TEMP_DIR)) {
+            byte[] bytes = new byte[]{0, 1, 2};
+            fbos.write(bytes[0]);
+            fbos.write(bytes[1]);
+
+            assertNull("Found unexpected temp file", findTempFileName());
+
+            fbos.write(bytes[2]);
+            fbos.flush();
+
+            assertNotNull("Expected temp file created", findTempFileName());
+
+            assertEquals("Size", bytes.length, fbos.asByteSource().size());
+            assertArrayEquals("Read bytes", bytes, fbos.asByteSource().read());
+        }
+
+        LOG.info("testFileThresholdReachedWithWriteByte ending");
+    }
+
+    @Test(expected = IOException.class)
+    public void testWriteAfterAsByteSource() throws IOException {
+        LOG.info("testWriteAfterAsByteSource starting");
+        try (FileBackedOutputStream fbos = new FileBackedOutputStream(3, TEMP_DIR)) {
+            byte[] bytes = new byte[]{0, 1, 2};
+            fbos.write(bytes);
+
+            assertNull("Found unexpected temp file", findTempFileName());
+            assertEquals("Size", bytes.length, fbos.asByteSource().size());
+
+            // Should throw IOException after call to asByteSource.
+            fbos.write(1);
+        }
+    }
+
+    @Test
+    public void testTempFileDeletedOnGC() throws IOException {
+        LOG.info("testTempFileDeletedOnGC starting");
+
+        FileBackedOutputStream fbos = null;
+        try {
+            fbos = new FileBackedOutputStream(1, TEMP_DIR);
+            fbos.write(new byte[] {0, 1});
+            assertNotNull("Expected temp file created", findTempFileName());
+        } finally {
+            if (fbos != null) {
+                fbos.close();
+            }
+            fbos = null;
+        }
+
+        Stopwatch sw = Stopwatch.createStarted();
+        while (sw.elapsed(TimeUnit.SECONDS) <= 20) {
+            System.gc();
+            if (findTempFileName() == null) {
+                return;
+            }
+            Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS);
+        }
+
+        fail("Temp file was not deleted");
+    }
+
+    private static String findTempFileName() {
+        String[] files = new File(TEMP_DIR).list();
+        assertNotNull(files);
+        assertTrue("Found more than one temp file: " + Arrays.toString(files), files.length < 2);
+        return files.length == 1 ? files[0] : null;
+    }
+
+    private static boolean deleteFile(String file) {
+        return new File(file).delete();
+    }
+
+    private static void deleteTempFiles() {
+        String[] files = new File(TEMP_DIR).list();
+        if (files != null) {
+            for (String file: files) {
+                deleteFile(TEMP_DIR + File.separator + file);
+            }
+        }
+    }
+}