/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* 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.cinchapi.concourse.server.plugin.io;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.cinchapi.common.reflect.Reflection;
import com.cinchapi.concourse.util.ByteBuffers;
import com.cinchapi.concourse.util.FileOps;
import com.cinchapi.concourse.util.Random;
import com.cinchapi.concourse.util.RandomStringGenerator;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
/**
* Unit tests for {@link SharedMemory}.
*
* @author Jeff Nelson
*/
public class SharedMemoryTest extends InterProcessCommunicationTest {
@Override
protected InterProcessCommunication getInterProcessCommunication() {
return new SharedMemory();
}
@Override
protected InterProcessCommunication getInterProcessCommunication(
String file) {
return new SharedMemory(file);
}
@Override
protected InterProcessCommunication getInterProcessCommunication(
String file, int capacity) {
return new SharedMemory(file, capacity);
}
@Test
public void testCompactionRunsInBackground()
throws InterruptedException, IOException {
int frequency = SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS;
SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS = 50;
try {
String file = FileOps.tempFile();
InterProcessCommunication sm = getInterProcessCommunication(file);
long size = Files.size(Paths.get(file));
sm.write(ByteBuffers.fromString("aaa"));
sm.write(ByteBuffers.fromString("bbb"));
sm.read();
Thread.sleep(50 + 1);
long lastCompaction = Reflection.get("lastCompaction", sm);
sm.write(ByteBuffers.fromString("ccc"));
while (Reflection.get("lastCompaction", sm)
.equals(lastCompaction)) {
continue; // wait for compaction
}
Assert.assertTrue(size > Files.size(Paths.get(file)));
InterProcessCommunication sm2 = getInterProcessCommunication(file);
Assert.assertEquals(ByteBuffers.fromString("bbb"), sm2.read());
Assert.assertEquals(ByteBuffers.fromString("ccc"), sm.read());
}
finally {
SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS = frequency;
}
}
@Test
public void testCompactionByReaderWontRuinWriter()
throws InterruptedException, IOException { // bug repro
int original = SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS;
SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS = 100;
try {
String file = FileOps.tempFile();
InterProcessCommunication writer = getInterProcessCommunication(
file, 4);
InterProcessCommunication reader = getInterProcessCommunication(
file, 4);
ByteBuffer message = ByteBuffers
.fromString(new RandomStringGenerator().nextString(15000));
writer.write(message);
message.flip();
Thread.sleep(SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS + 10); // ensure
// compaction
// starts
reader.read();
writer.write(message);
message.flip();
ByteBuffer actual = reader.read();
Assert.assertEquals(message, actual);
}
finally {
SharedMemory.COMPACTION_FREQUENCY_IN_MILLIS = original;
}
}
@Test
public void testCompaction() {
int toRead = Random.getScaleCount();
int total = toRead + Random.getScaleCount();
InterProcessCommunication memory = getInterProcessCommunication();
List<String> expected = Lists.newArrayList();
for (int i = 0; i < total; ++i) {
String message = Random.getString();
memory.write(ByteBuffers.fromString(message));
expected.add(message);
}
List<String> actual = Lists
.newArrayListWithExpectedSize(expected.size());
for (int i = 0; i < toRead; ++i) {
String message = ByteBuffers.getString(memory.read());
actual.add(message);
}
int pos0 = ((MappedAtomicInteger) Reflection.get("nextRead", memory))
.get();
memory.compact();
int pos1 = ((MappedAtomicInteger) Reflection.get("nextRead", memory))
.get();
Assert.assertTrue(pos0 > pos1);
for (int i = toRead; i < total; ++i) {
String message = ByteBuffers.getString(memory.read());
actual.add(message);
}
Assert.assertEquals(expected, actual);
}
@Test
public void testCompactionDecreasesUnderlyingFileSize() {
String file = FileOps.tempFile();
InterProcessCommunication memory = getInterProcessCommunication(file);
try {
memory.write(ByteBuffers.fromString("hello world"));
long size = Files.size(Paths.get(file));
memory.read();
memory.compact();
Assert.assertTrue(size > Files.size(Paths.get(file)));
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
@Test
public void testCompactionIsReflectedAcrossInstances() {
String file = FileOps.tempFile();
InterProcessCommunication sm1 = getInterProcessCommunication(file);
InterProcessCommunication sm2 = getInterProcessCommunication(file);
sm1.write(ByteBuffers.fromString("aaa"));
sm2.write(ByteBuffers.fromString("bbb"));
sm1.read();
sm1.compact();
Assert.assertEquals(sm2.read(), ByteBuffers.fromString("bbb"));
sm2.write(ByteBuffers.fromString("cc"));
sm2.compact();
Assert.assertEquals(sm1.read(), ByteBuffers.fromString("cc"));
}
@Test
public void testCompactionAcrossInstancesForWrites() {
String file = FileOps.tempFile();
InterProcessCommunication sm1 = getInterProcessCommunication(file);
InterProcessCommunication sm2 = getInterProcessCommunication(file);
sm1.write(ByteBuffers.fromString("aaa"));
sm1.write(ByteBuffers.fromString("bbb"));
sm1.write(ByteBuffers.fromString("ccc"));
sm1.read();
sm2.read();
sm1.compact();
sm2.write(ByteBuffers.fromString("dddd"));
sm1.write(ByteBuffers.fromString("ee"));
Assert.assertEquals(sm2.read(), ByteBuffers.fromString("ccc"));
Assert.assertEquals(sm1.read(), ByteBuffers.fromString("dddd"));
Assert.assertEquals(sm1.read(), ByteBuffers.fromString("ee"));
}
}