/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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.
*/
package com.hazelcast.nio;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.nio.serialization.Data;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import com.hazelcast.test.annotation.QuickTest;
import com.hazelcast.util.EmptyStatement;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import static com.hazelcast.internal.serialization.impl.SerializationUtil.createObjectDataInputStream;
import static com.hazelcast.internal.serialization.impl.SerializationUtil.createObjectDataOutputStream;
import static com.hazelcast.nio.IOUtil.closeResource;
import static com.hazelcast.nio.IOUtil.compress;
import static com.hazelcast.nio.IOUtil.copy;
import static com.hazelcast.nio.IOUtil.copyFile;
import static com.hazelcast.nio.IOUtil.decompress;
import static com.hazelcast.nio.IOUtil.delete;
import static com.hazelcast.nio.IOUtil.deleteQuietly;
import static com.hazelcast.nio.IOUtil.getFileFromResources;
import static com.hazelcast.nio.IOUtil.newInputStream;
import static com.hazelcast.nio.IOUtil.newOutputStream;
import static com.hazelcast.nio.IOUtil.readByteArray;
import static com.hazelcast.nio.IOUtil.readFully;
import static com.hazelcast.nio.IOUtil.readFullyOrNothing;
import static com.hazelcast.nio.IOUtil.readObject;
import static com.hazelcast.nio.IOUtil.toFileName;
import static com.hazelcast.nio.IOUtil.writeByteArray;
import static com.hazelcast.nio.IOUtil.writeObject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
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 static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@RunWith(HazelcastSerialClassRunner.class)
@Category(QuickTest.class)
public class IOUtilTest extends HazelcastTestSupport {
private static final byte[] NON_EMPTY_BYTE_ARRAY = new byte[100];
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final int SIZE = 3;
private static TestHazelcastInstanceFactory hazelcastInstanceFactory;
private static InternalSerializationService serializationService;
@BeforeClass
public static void setUp() {
hazelcastInstanceFactory = new TestHazelcastInstanceFactory();
HazelcastInstance hazelcastInstance = hazelcastInstanceFactory.newHazelcastInstance();
serializationService = getSerializationService(hazelcastInstance);
}
@AfterClass
public static void tearDown() {
hazelcastInstanceFactory.shutdownAll();
}
@Test
public void testConstructor() {
assertUtilityConstructor(IOUtil.class);
}
@Test
public void testWriteAndReadByteArray() throws Exception {
byte[] bytes = new byte[SIZE];
bytes[0] = SIZE - 1;
bytes[1] = 23;
bytes[2] = 42;
byte[] output = writeAndReadByteArray(bytes);
assertNotNull(output);
assertEquals(SIZE - 1, output[0]);
assertEquals(23, output[1]);
assertEquals(42, output[2]);
}
@Test
public void testWriteAndReadByteArray_withNull() throws Exception {
byte[] output = writeAndReadByteArray(null);
assertNull(output);
}
private static byte[] writeAndReadByteArray(byte[] bytes) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectDataOutput out = createObjectDataOutputStream(bout, serializationService);
writeByteArray(out, bytes);
byte[] data = bout.toByteArray();
ByteArrayInputStream bin = new ByteArrayInputStream(data);
ObjectDataInput in = createObjectDataInputStream(bin, serializationService);
return readByteArray(in);
}
@Test
public void testWriteAndReadObject() throws Exception {
String expected = "test input";
String actual = (String) writeAndReadObject(expected);
assertNotNull(actual);
assertEquals(expected, actual);
}
@Test
public void testWriteAndReadObject_withData() throws Exception {
Data expected = serializationService.toData("test input");
Data actual = (Data) writeAndReadObject(expected);
assertNotNull(actual);
assertEquals(expected, actual);
}
private static Object writeAndReadObject(Object input) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectDataOutput out = createObjectDataOutputStream(bout, serializationService);
writeObject(out, input);
byte[] data = bout.toByteArray();
ByteArrayInputStream bin = new ByteArrayInputStream(data);
ObjectDataInput in = createObjectDataInputStream(bin, serializationService);
return readObject(in);
}
private final byte[] streamInput = {1, 2, 3, 4};
@Test
public void testReadFullyOrNothing() throws Exception {
InputStream in = new ByteArrayInputStream(streamInput);
byte[] buffer = new byte[4];
boolean result = readFullyOrNothing(in, buffer);
assertTrue(result);
for (int i = 0; i < buffer.length; i++) {
assertEquals(buffer[i], streamInput[i]);
}
}
@Test
public void testReadFullyOrNothing_whenThereIsNoData_thenReturnFalse() throws Exception {
InputStream in = new ByteArrayInputStream(new byte[0]);
byte[] buffer = new byte[4];
boolean result = readFullyOrNothing(in, buffer);
assertFalse(result);
}
@Test(expected = EOFException.class)
public void testReadFullyOrNothing_whenThereIsNotEnoughData_thenThrowException() throws Exception {
InputStream in = new ByteArrayInputStream(streamInput);
byte[] buffer = new byte[8];
readFullyOrNothing(in, buffer);
}
@Test
public void testReadFully() throws Exception {
InputStream in = new ByteArrayInputStream(streamInput);
byte[] buffer = new byte[4];
readFully(in, buffer);
for (int i = 0; i < buffer.length; i++) {
assertEquals(buffer[i], streamInput[i]);
}
}
@Test(expected = EOFException.class)
public void testReadFully_whenThereIsNoData_thenThrowException() throws Exception {
InputStream in = new ByteArrayInputStream(new byte[0]);
byte[] buffer = new byte[4];
readFully(in, buffer);
}
@Test(expected = EOFException.class)
public void testReadFully_whenThereIsNotEnoughData_thenThrowException() throws Exception {
InputStream in = new ByteArrayInputStream(streamInput);
byte[] buffer = new byte[8];
readFully(in, buffer);
}
@Test
public void testNewOutputStream_shouldWriteWholeByteBuffer() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
OutputStream outputStream = newOutputStream(buffer);
assertEquals(SIZE, buffer.remaining());
outputStream.write(new byte[SIZE]);
assertEquals(0, buffer.remaining());
}
@Test
public void testNewOutputStream_shouldWriteSingleByte() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
OutputStream outputStream = newOutputStream(buffer);
assertEquals(SIZE, buffer.remaining());
outputStream.write(23);
assertEquals(SIZE - 1, buffer.remaining());
}
@Test
public void testNewOutputStream_shouldWriteInChunks() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
OutputStream outputStream = newOutputStream(buffer);
assertEquals(SIZE, buffer.remaining());
outputStream.write(new byte[1], 0, 1);
outputStream.write(new byte[SIZE - 1], 0, SIZE - 1);
assertEquals(0, buffer.remaining());
}
@Test(expected = BufferOverflowException.class)
public void testNewOutputStream_shouldThrowWhenTryingToWriteToEmptyByteBuffer() throws Exception {
ByteBuffer empty = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
OutputStream outputStream = newOutputStream(empty);
outputStream.write(23);
}
@Test
public void testNewInputStream_shouldReturnMinusOneWhenEmptyByteBufferProvidedAndReadingOneByte() throws Exception {
ByteBuffer empty = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
InputStream inputStream = newInputStream(empty);
int read = inputStream.read();
assertEquals(-1, read);
}
@Test
public void testNewInputStream_shouldReadWholeByteBuffer() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
InputStream inputStream = newInputStream(buffer);
int read = inputStream.read(new byte[SIZE]);
assertEquals(SIZE, read);
}
@Test
public void testNewInputStream_shouldAllowReadingByteBufferInChunks() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
InputStream inputStream = newInputStream(buffer);
int firstRead = inputStream.read(new byte[1]);
int secondRead = inputStream.read(new byte[SIZE - 1]);
assertEquals(1, firstRead);
assertEquals(SIZE - 1, secondRead);
}
@Test
public void testNewInputStream_shouldReturnMinusOneWhenNothingRemainingInByteBuffer() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
InputStream inputStream = newInputStream(buffer);
int firstRead = inputStream.read(new byte[SIZE]);
int secondRead = inputStream.read();
assertEquals(SIZE, firstRead);
assertEquals(-1, secondRead);
}
@Test
public void testNewInputStream_shouldReturnMinusOneWhenEmptyByteBufferProvidedAndReadingSeveralBytes() throws Exception {
ByteBuffer empty = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
InputStream inputStream = newInputStream(empty);
int read = inputStream.read(NON_EMPTY_BYTE_ARRAY);
assertEquals(-1, read);
}
@Test(expected = EOFException.class)
public void testNewInputStream_shouldThrowWhenTryingToReadFullyFromEmptyByteBuffer() throws Exception {
ByteBuffer empty = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
DataInputStream inputStream = new DataInputStream(newInputStream(empty));
inputStream.readFully(NON_EMPTY_BYTE_ARRAY);
}
@Test(expected = EOFException.class)
public void testNewInputStream_shouldThrowWhenByteBufferExhaustedAndTryingToReadFully() throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(new byte[SIZE]);
DataInputStream inputStream = new DataInputStream(newInputStream(buffer));
inputStream.readFully(new byte[SIZE]);
inputStream.readFully(NON_EMPTY_BYTE_ARRAY);
}
@Test
public void testCompressAndDecompress() throws Exception {
String expected = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born"
+ " and I will give you a complete account of the system, and expound the actual teachings of the great explorer"
+ " of the truth, the master-builder of human happiness.";
byte[] compressed = compress(expected.getBytes());
byte[] decompressed = decompress(compressed);
assertEquals(expected, new String(decompressed));
}
@Test
public void testCompressAndDecompress_withEmptyString() throws Exception {
String expected = "";
byte[] compressed = compress(expected.getBytes());
byte[] decompressed = decompress(compressed);
assertEquals(expected, new String(decompressed));
}
@Test
public void testCloseResource() throws Exception {
Closeable closeable = mock(Closeable.class);
closeResource(closeable);
verify(closeable).close();
verifyNoMoreInteractions(closeable);
}
@Test
public void testCloseResource_withException() throws Exception {
Closeable closeable = mock(Closeable.class);
doThrow(new IOException("expected")).when(closeable).close();
closeResource(closeable);
verify(closeable).close();
verifyNoMoreInteractions(closeable);
}
@Test
public void testCloseResource_withNull() {
closeResource(null);
}
@Test
public void testDelete_shouldDoNothingWithNonExistentFile() {
File file = new File("notFound");
delete(file);
}
@Test
public void testDelete_shouldDeleteDirectoryRecursively() throws Exception {
File parentDir = createDirectory("parent");
File file1 = createFile(parentDir, "file1");
File file2 = createFile(parentDir, "file2");
File childDir = createDirectory(parentDir, "child");
File childFile1 = createFile(childDir, "childFile1");
File childFile2 = createFile(childDir, "childFile2");
delete(parentDir);
assertFalse(parentDir.exists());
assertFalse(file1.exists());
assertFalse(file2.exists());
assertFalse(childDir.exists());
assertFalse(childFile1.exists());
assertFalse(childFile2.exists());
}
@Test(expected = IllegalArgumentException.class)
public void testCopyFailsWhenSourceDoesntExist() {
copy(new File("nonExistant"), new File("target"));
}
@Test
public void testCopyFileFailsWhenTargetDoesntExistAndCannotBeCreated() throws IOException {
final File target = mock(File.class);
when(target.exists()).thenReturn(false);
when(target.mkdirs()).thenReturn(false);
final File source = new File("source");
assertTrue(!source.exists());
source.createNewFile();
try {
copyFile(source, target, -1);
fail();
} catch (HazelcastException expected) {
EmptyStatement.ignore(expected);
}
delete(source);
}
@Test
public void testCopyFailsWhenSourceCannotBeListed() throws IOException {
final File source = mock(File.class);
when(source.exists()).thenReturn(true);
when(source.isDirectory()).thenReturn(true);
when(source.listFiles()).thenReturn(null);
when(source.getName()).thenReturn("dummy");
final File dest = new File("dest");
assertTrue(!dest.exists());
dest.mkdir();
try {
copy(source, dest);
fail();
} catch (HazelcastException expected) {
EmptyStatement.ignore(expected);
}
delete(dest);
}
@Test(expected = IllegalArgumentException.class)
public void testCopyFileFailsWhenSourceDoesntExist() {
copyFile(new File("nonExistant"), new File("target"), -1);
}
@Test
public void testCopyFileFailsWhenSourceIsNotAFile() {
final File source = new File("source");
assertTrue(!source.exists());
source.mkdirs();
try {
copyFile(source, new File("target"), -1);
fail();
} catch (IllegalArgumentException expected) {
EmptyStatement.ignore(expected);
}
delete(source);
}
@Test
public void testCopyFailsWhenSourceIsDirAndTargetIsFile() throws IOException {
final File source = new File("dir1");
final File target = new File("file1");
assertTrue(!source.exists() && !target.exists());
source.mkdir();
target.createNewFile();
try {
copy(source, target);
fail();
} catch (IllegalArgumentException expected) {
EmptyStatement.ignore(expected);
}
delete(source);
delete(target);
}
@Test
public void testCopyRecursiveDirectory() throws IOException {
final File dir = new File("dir");
final File subdir = new File(dir, "subdir");
final File f1 = new File(dir, "f1");
final File f2 = new File(subdir, "f2");
assertTrue(!dir.exists());
assertTrue(!subdir.exists());
dir.mkdir();
subdir.mkdir();
writeTo(f1, "testContent");
writeTo(f2, "otherContent");
final File copy = new File("copy");
assertTrue(!copy.exists());
copy(dir, copy);
assertTrue(copy.exists());
assertEqualFiles(dir, new File(copy, "dir"));
delete(dir);
delete(subdir);
delete(copy);
}
@Test
public void testDelete_shouldDeleteSingleFile() throws Exception {
File file = createFile("singleFile");
delete(file);
assertFalse(file.exists());
}
@Test(expected = HazelcastException.class)
public void testDelete_shouldThrowIfFileCouldNotBeDeleted() {
File file = mock(File.class);
when(file.exists()).thenReturn(true);
when(file.delete()).thenReturn(false);
delete(file);
}
@Test
public void testDeleteQuietly_shouldDeleteSingleFile() throws Exception {
File file = createFile("singleFile");
deleteQuietly(file);
assertFalse(file.exists());
}
@Test
public void testDeleteQuietly_shouldDoNothingIfFileCouldNotBeDeleted() {
File file = mock(File.class);
when(file.exists()).thenReturn(true);
when(file.delete()).thenReturn(false);
deleteQuietly(file);
}
private static File createDirectory(String dirName) throws IOException {
File dir = new File(dirName);
return createDirectory(dir);
}
private static File createDirectory(File parent, String dirName) throws IOException {
File dir = new File(parent, dirName);
return createDirectory(dir);
}
private static File createDirectory(File dir) {
if (dir.isDirectory()) {
return dir;
}
if (!dir.mkdirs() || !dir.exists()) {
fail("Could not create directory " + dir.getAbsolutePath());
}
return dir;
}
private static File createFile(String fileName) throws IOException {
File file = new File(fileName);
return createFile(file);
}
private static File createFile(File parent, String fileName) throws IOException {
File file = new File(parent, fileName);
return createFile(file);
}
private static File createFile(File file) throws IOException {
if (file.isFile()) {
return file;
}
if (!file.createNewFile() || !file.exists()) {
fail("Could not create file " + file.getAbsolutePath());
}
return file;
}
@Test
public void testToFileName_shouldNotChangeValidFileName() {
String expected = "valid-fileName_23.txt";
String actual = toFileName(expected);
assertEquals(expected, actual);
}
@Test
public void testToFileName_shouldChangeInvalidFileName() {
String expected = "a_b_c_d_e_f_g_h_j_k_l_m.txt";
String actual = toFileName("a:b?c*d\"e|f<g>h'j,k\\l/m.txt");
assertEquals(expected, actual);
}
@Test
public void testGetFileFromResources_shouldReturnExistingFile() {
File file = getFileFromResources("logging.properties");
assertTrue(file.exists());
}
@Test(expected = HazelcastException.class)
public void testGetFileFromResources_shouldThrowExceptionIfFileDoesNotExist() {
getFileFromResources("doesNotExist");
}
private static void writeTo(File f1, String testContent) {
FileWriter w = null;
try {
w = new FileWriter(f1);
w.write(testContent);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
closeResource(w);
}
}
private static void assertEqualFiles(File f1, File f2) {
if (f1.exists()) {
assertTrue(f2.exists());
}
assertTrue(f1.getName().equals(f2.getName()));
if (f1.isFile()) {
assertTrue(f2.isFile());
if (!equalContents(f1, f2)) {
fail();
}
return;
}
final File[] f1Files = f1.listFiles();
assertTrue(f1Files.length == f2.listFiles().length);
for (File f : f1Files) {
assertEqualFiles(f, new File(f2, f.getName()));
}
}
// note: use only for small files (e.g. up to a couple of hundred KBs). See below.
private static boolean equalContents(File f1, File f2) {
InputStream is1 = null;
InputStream is2 = null;
try {
is1 = new FileInputStream(f1);
is2 = new FileInputStream(f2);
// compare byte-by-byte since InputStream.read(byte[]) possibly doesn't return the requested number of bytes
// this is why this method should be used for smallFiles
int data;
while ((data = is1.read()) != -1) {
if (data != is2.read()) {
return false;
}
}
if (is2.read() != -1) {
return false;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
closeResource(is1);
closeResource(is2);
}
return true;
}
}