package io.eguan.vvr.repository.core.api;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.utils.ByteArrays;
import io.eguan.vvr.repository.core.api.Device;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.AssertionFailedError;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests to read and write in VVR {@link Device}.
*
* @author oodrive
* @author llambert
*
*/
public abstract class TestDeviceIoAbstract extends TestDeviceAbstract {
protected TestDeviceIoAbstract(final boolean helpersErr) {
super(helpersErr);
}
/**
* Write datas in the device and in a file, than compare the contents of the file and the device.
*
* @throws IOException
*/
@Test
public void testReadWrite() throws IOException {
final File fileTmp = File.createTempFile("vvrIO", ".tmp");
try {
try (RandomAccessFile raf = new RandomAccessFile(fileTmp, "rw")) {
// Set file size
raf.setLength(device.getSize());
// Compare: should have only 0
compareDirect(fileTmp, device, deviceBlockSize);
compare(fileTmp, device, deviceBlockSize);
final byte[] buffer = new byte[deviceBlockSize * 4];
final Random random = new SecureRandom();
random.nextBytes(buffer);
try (Device.ReadWriteHandle handle = device.open(true)) {
// Write first block
long position = 0L;
int bufOffset = 0;
int bufLength = deviceBlockSize;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Write last block
position = device.getSize() - deviceBlockSize;
bufOffset = deviceBlockSize;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Write a block in the middle
position = 3 * deviceBlockSize;
bufOffset = 2 * deviceBlockSize;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Same block in the middle, a second time
position = 3 * deviceBlockSize;
bufOffset = 2 * deviceBlockSize;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Write a portion of a block
position = 2 * deviceBlockSize + 5;
bufOffset = deviceBlockSize / 5 + 2;
bufLength = deviceBlockSize / 6 + 12;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Write two blocks, aligned
position = 6 * deviceBlockSize;
bufOffset = deviceBlockSize + 512;
bufLength = deviceBlockSize * 2;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Write two blocks, not aligned
position += deviceBlockSize / 11;
bufOffset = deviceBlockSize + 112;
bufLength = deviceBlockSize * 2;
write(buffer, position, bufOffset, bufLength, raf, handle);
device.createSnapshot("Test snap");
// Write some data, not aligned
position += deviceBlockSize;
bufOffset = 12;
bufLength = deviceBlockSize * 3 + 33;
write(buffer, position, bufOffset, bufLength, raf, handle);
// Rewrite some data, not aligned
position -= deviceBlockSize / 2;
bufOffset = 14;
bufLength = deviceBlockSize + 33;
write(buffer, position, bufOffset, bufLength, raf, handle);
}
}
// Compare, read device with direct buffer
// Read, block by block
compareDirect(fileTmp, device, deviceBlockSize);
// Read, 2 blocks at a time
compareDirect(fileTmp, device, 2 * deviceBlockSize);
// Read, less than one block
compareDirect(fileTmp, device, deviceBlockSize / 11 + 3);
// Read, more than one block
compareDirect(fileTmp, device, deviceBlockSize + deviceBlockSize / 5 + 3);
// Compare, read device with HeapBuffer
// Read, block by block
compare(fileTmp, device, deviceBlockSize);
// Read, 2 blocks at a time
compare(fileTmp, device, 2 * deviceBlockSize);
// Read, less than one block
compare(fileTmp, device, deviceBlockSize / 11 + 3);
// Read, more than one block
compare(fileTmp, device, deviceBlockSize + deviceBlockSize / 5 + 3);
}
finally {
fileTmp.delete();
}
}
private static final void write(final byte[] data, final long position, final int offset, final int length,
final RandomAccessFile raf, final Device.ReadWriteHandle handle) throws IOException {
// Write in file
LOGGER.info("Write pos=" + position + ", offset=" + offset + ", length=" + length);
raf.seek(position);
raf.write(data, offset, length);
// Write in device
final ByteBuffer src = ByteBuffer.wrap(data);
handle.write(src, offset, length, position);
}
private static final void compare(final File file, final Device device, final int readSize)
throws FileNotFoundException, IOException {
final byte[] bufFile = new byte[readSize];
final byte[] bufDev = new byte[readSize];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
long devOffset = 0;
try (Device.ReadWriteHandle handle = device.open(false)) {
try (FileInputStream fis = new FileInputStream(file)) {
int readLen;
while ((readLen = fis.read(bufFile)) != -1) {
bds.rewind();
handle.read(bds, 0, readLen, devOffset);
// Compare read bytes
ByteArrays.assertEqualsByteArrays("devOffset=" + devOffset + " ", bufFile, bufDev);
devOffset += readLen;
}
}
}
}
private static final void compareDirect(final File file, final Device device, final int readSize)
throws FileNotFoundException, IOException {
final byte[] bufFile = new byte[readSize];
final ByteBuffer bds = ByteBuffer.allocateDirect(readSize);
long devOffset = 0;
try (Device.ReadWriteHandle handle = device.open(false)) {
try (FileInputStream fis = new FileInputStream(file)) {
int readLen;
while ((readLen = fis.read(bufFile)) != -1) {
bds.rewind();
handle.read(bds, 0, readLen, devOffset);
// Compare read bytes
for (int i = 0; i < readLen; i++) {
Assert.assertEquals("devOffset=" + devOffset + i, bufFile[i], bds.get(i));
}
devOffset += readLen;
}
}
}
}
/**
* Reads and writes data from different threads on the same device handle.
*
* @throws IOException
* @throws InterruptedException
*/
@Test
public void testConcurrentReadWrite() throws IOException, InterruptedException {
final Random random = new SecureRandom();
final int wlen1 = deviceBlockSize * 32;
final int wlen2 = deviceBlockSize * 16;
final int rlen = deviceBlockSize * 64;
final long offsetW1 = deviceBlockSize / 2L;
final long offsetW2 = deviceBlockSize / 2L + wlen1 + deviceBlockSize;
final long offsetWOverride = deviceBlockSize / 2L + wlen1 - deviceBlockSize;
final long offsetR3 = 0L;
final byte[] writeBuf1 = new byte[wlen1];
random.nextBytes(writeBuf1);
final byte[] writeBuf2 = new byte[wlen2];
random.nextBytes(writeBuf2);
final byte[] readBuf1 = new byte[rlen];
final byte[] readBuf2 = new byte[rlen];
final byte[] readBuf3 = new byte[rlen];
{ // First write2
System.arraycopy(writeBuf2, 0, readBuf1, 0, wlen2);
}
{ // First write2+override
System.arraycopy(writeBuf2, 0, readBuf2, 0, wlen2);
final int deltaOffset = (int) (offsetW2 - offsetWOverride);
System.arraycopy(writeBuf2, deltaOffset, readBuf2, 0, wlen2 - deltaOffset);
}
{ // write2
int destOffset = (int) (offsetW2 - offsetR3);
System.arraycopy(writeBuf2, 0, readBuf3, destOffset, wlen2);
// override
destOffset = (int) (offsetWOverride - offsetR3);
System.arraycopy(writeBuf2, 0, readBuf3, destOffset, wlen2);
// write1
destOffset = (int) (offsetW1 - offsetR3);
System.arraycopy(writeBuf1, 0, readBuf3, destOffset, wlen1);
}
try (final Device.ReadWriteHandle handle = device.open(true)) {
// Init device contents for read
{
ByteBuffer source = ByteBuffer.wrap(writeBuf1);
handle.write(source, 0, wlen1, offsetW1);
source = ByteBuffer.wrap(writeBuf2);
handle.write(source, 0, wlen2, offsetW2);
}
// Reader
final AtomicReference<Throwable> readerRef = new AtomicReference<>();
final Thread reader;
{
reader = new Thread(new Runnable() {
@Override
public final void run() {
try {
final byte[] readBuf = new byte[rlen];
final ByteBuffer destination = ByteBuffer.wrap(readBuf);
do {
destination.rewind();
handle.read(destination, 0, rlen, offsetW2);
Assert.assertEquals(rlen, destination.position());
} while (Arrays.equals(readBuf1, readBuf));
// Override done: get device final contents
destination.rewind();
handle.read(destination, 0, rlen, offsetR3);
ByteArrays.assertEqualsByteArrays(readBuf3, readBuf);
}
catch (final Throwable t) {
LOGGER.warn("Reader unexpected end", t);
readerRef.set(t);
}
}
}, "Reader");
reader.setDaemon(true);
reader.start();
Thread.yield();
}
// Writer1 - write first area, no conflict with read
final AtomicReference<Throwable> writer1Ref = new AtomicReference<>();
final Thread writer1;
{
writer1 = new Thread(new Runnable() {
@Override
public final void run() {
try {
Thread.sleep(100);
final ByteBuffer source = ByteBuffer.wrap(writeBuf1);
while (reader.isAlive()) {
source.rewind();
handle.write(source, 0, wlen1, offsetW1);
Assert.assertEquals(wlen1, source.position());
}
}
catch (final Throwable t) {
LOGGER.warn("Writer1 unexpected end", t);
writer1Ref.set(t);
}
}
}, "Writer1");
writer1.setDaemon(true);
writer1.start();
}
// Writer2 - write first area, conflict with read (add delay in loop)
final AtomicReference<Throwable> writer2Ref = new AtomicReference<>();
final CyclicBarrier writer2Stop = new CyclicBarrier(2);
final Thread writer2;
{
writer2 = new Thread(new Runnable() {
@Override
public final void run() {
try {
final ByteBuffer source = ByteBuffer.wrap(writeBuf2);
while (writer2Stop.getNumberWaiting() == 0) {
if (!reader.isAlive()) {
return;
}
// Wait and write
Thread.sleep(100);
source.rewind();
handle.write(source, 0, wlen2, offsetW2);
Assert.assertEquals(wlen2, source.position());
}
// Trip barrier
writer2Stop.await();
}
catch (final Throwable t) {
LOGGER.warn("Writer2 unexpected end", t);
writer2Ref.set(t);
}
}
}, "Writer2");
writer2.setDaemon(true);
writer2.start();
}
// Writer override: write one shot
final AtomicReference<Throwable> writerOverrideRef = new AtomicReference<>();
final Thread writerOverride;
{
writerOverride = new Thread(new Runnable() {
@Override
public final void run() {
try {
Thread.sleep(2000);
// Wait for end of writer2
writer2Stop.await();
final ByteBuffer source = ByteBuffer.wrap(writeBuf2);
handle.write(source, 0, wlen2, offsetWOverride);
Assert.assertEquals(wlen2, source.position());
}
catch (final Throwable t) {
LOGGER.warn("WriterOverride unexpected end", t);
writerOverrideRef.set(t);
}
}
}, "Writer override");
writerOverride.setDaemon(true);
writerOverride.start();
}
reader.join();
writer1.join();
writer2.join();
while (writerOverride.isAlive()) {
writer2Stop.reset(); // Make sure the thread is not waiting on the barrier
writerOverride.join(500);
}
assertRunOk("Reader", readerRef);
assertRunOk("Writer1", writer1Ref);
assertRunOk("Writer2", writer2Ref);
assertRunOk("WriterOverride", writerOverrideRef);
// Check device contents
{
final byte[] readBuf = new byte[wlen1];
final ByteBuffer destination = ByteBuffer.wrap(readBuf);
handle.read(destination, 0, wlen1, offsetW1);
ByteArrays.assertEqualsByteArrays(writeBuf1, readBuf);
}
{
final byte[] readBuf = new byte[rlen];
final ByteBuffer destination = ByteBuffer.wrap(readBuf);
handle.read(destination, 0, rlen, offsetW2);
ByteArrays.assertEqualsByteArrays(readBuf2, readBuf);
}
{
final byte[] readBuf = new byte[rlen];
final ByteBuffer destination = ByteBuffer.wrap(readBuf);
handle.read(destination, 0, rlen, offsetR3);
ByteArrays.assertEqualsByteArrays(readBuf3, readBuf);
}
}
}
private final void assertRunOk(final String threadName, final AtomicReference<Throwable> throwableRef) {
final Throwable runThrowable = throwableRef.get();
if (runThrowable != null) {
final AssertionFailedError afe = new AssertionFailedError(threadName);
afe.initCause(runThrowable);
throw afe;
}
}
@Test(expected = IOException.class)
public void testReadClosed() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 0;
final Device.ReadWriteHandle handle = device.open(true);
handle.close();
handle.read(bds, 0, len, devOffset);
}
@Test(expected = IOException.class)
public void testWriteClosed() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 0;
final Device.ReadWriteHandle handle = device.open(true);
handle.close();
handle.write(bds, 0, len, devOffset);
}
@Test(expected = IOException.class)
public void testWriteOpenedReadOnly() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 0;
try (final Device.ReadWriteHandle handle = device.open(false)) {
handle.write(bds, 0, len, devOffset);
}
}
@Test(expected = IOException.class)
public void testWriteDevOffsetNegative() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = -5;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(bds, 0, len, devOffset);
}
}
@Test(expected = IOException.class)
public void testWriteBufOffsetNegative() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 5;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(bds, -1, len, devOffset);
}
}
@Test(expected = IOException.class)
public void testWriteLenNegative() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 5;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(bds, 0, -12, devOffset);
}
}
@Test(expected = IOException.class)
public void testWriteDevOffsetOverflow() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = device.getSize() - 221;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(bds, 0, len, devOffset);
}
}
@Test(expected = IOException.class)
public void testWriteBufOffsetOverflow() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 5;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(bds, len / 2, len, devOffset);
}
}
@Test(expected = NullPointerException.class)
public void testWriteNullBuffer() throws IOException {
final int len = 14 * 1024;
final long devOffset = 5;
try (final Device.ReadWriteHandle handle = device.open(true)) {
handle.write(null, 0, len, devOffset);
}
}
@Test(expected = NullPointerException.class)
public void testReadNullBuffer() throws IOException {
final int len = 14 * 1024;
final long devOffset = 5;
try (final Device.ReadWriteHandle handle = device.open(false)) {
handle.read(null, 0, len, devOffset);
}
}
@Test
public void testWriteNullLen() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final Random random = new SecureRandom();
random.nextBytes(bufDev);
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final byte[] bufCheck = Arrays.copyOf(bufDev, len);
final byte[] bufRead = new byte[len];
final ByteBuffer bdr = ByteBuffer.wrap(bufRead);
final long devOffset = 22;
try (final Device.ReadWriteHandle handle = device.open(true)) {
// Write random contents
handle.write(bds, 0, len, devOffset);
Assert.assertEquals(len, bds.position());
// Read contents
handle.read(bdr, 0, len, devOffset);
Assert.assertTrue(Arrays.equals(bufCheck, bufRead));
Assert.assertEquals(len, bdr.position());
// Empty write
bds.rewind();
handle.write(bds, 2, 0, devOffset);
Assert.assertEquals(0, bds.position());
// Read contents again
bdr.rewind();
handle.read(bdr, 0, len, devOffset);
Assert.assertEquals(len, bdr.position());
Assert.assertTrue(Arrays.equals(bufCheck, bufRead));
}
}
@Test
public void testReadNullLen() throws IOException {
final int len = 14 * 1024;
final byte[] bufDev = new byte[len];
final Random random = new SecureRandom();
random.nextBytes(bufDev);
final byte[] bufCheck = Arrays.copyOf(bufDev, len);
final ByteBuffer bds = ByteBuffer.wrap(bufDev);
final long devOffset = 5;
// Read one byte to check read
try (final Device.ReadWriteHandle handle = device.open(true)) {
// Should not change contents when reading one byte
handle.read(bds, 0, 0, devOffset);
Assert.assertTrue(Arrays.equals(bufCheck, bufDev));
// Read one byte to check read and compare
handle.read(bds, 1, 1, devOffset);
Assert.assertFalse(Arrays.equals(bufCheck, bufDev));
Assert.assertEquals(0, bufDev[1]);
}
}
}