package openmods.structured;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import com.google.common.collect.Lists;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import openmods.structured.Command.Type;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InOrder;
public class StructuredTest {
public static class IdHolder {
public int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public abstract static class TestElement implements IStructureElement {
}
public static class IntTestElement extends TestElement {
public int value;
@Override
public void readFromStream(DataInput input) throws IOException {
this.value = input.readInt();
}
@Override
public void writeToStream(DataOutput output) throws IOException {
output.writeInt(value);
}
}
public static class StringTestElement extends TestElement {
public String value;
@Override
public void readFromStream(DataInput input) throws IOException {
this.value = input.readUTF();
}
@Override
public void writeToStream(DataOutput output) throws IOException {
output.writeUTF(value);
}
}
public abstract static class TestContainer implements IStructureContainer<TestElement> {}
public static class IntTestContainer extends TestContainer {
public static final int TYPE = 0;
public final IntTestElement element = new IntTestElement();
@Override
public int getType() {
return TYPE;
}
@Override
public void createElements(IElementAddCallback<TestElement> callback) {
callback.addElement(element);
}
}
public static class StringTestContainer extends TestContainer {
public static final int TYPE = 1;
public final StringTestElement element = new StringTestElement();
@Override
public int getType() {
return TYPE;
}
@Override
public void createElements(IElementAddCallback<TestElement> callback) {
callback.addElement(element);
}
}
public static class CustomDataTestContainer extends TestContainer implements ICustomCreateData {
public static final int TYPE = 2;
public final StringTestElement stringElement = new StringTestElement();
public final IntTestElement intElement = new IntTestElement();
public long customValue;
@Override
public int getType() {
return TYPE;
}
@Override
public void createElements(IElementAddCallback<TestElement> callback) {
callback.addElement(stringElement);
callback.addElement(intElement);
}
@Override
public void readCustomDataFromStream(DataInput input) throws IOException {
this.customValue = input.readLong();
}
@Override
public void writeCustomDataFromStream(DataOutput output) throws IOException {
output.writeLong(customValue);
}
}
public static class TestMaster extends StructuredDataMaster<TestContainer, TestElement> {
public TestMaster(IStructureObserver<TestContainer, TestElement> observer) {
super(observer);
}
public TestMaster() {
super();
}
}
public static class TestFactory implements IStructureContainerFactory<TestContainer> {
@Override
public TestContainer createContainer(int type) {
switch (type) {
case IntTestContainer.TYPE:
return new IntTestContainer();
case StringTestContainer.TYPE:
return new StringTestContainer();
case CustomDataTestContainer.TYPE:
return new CustomDataTestContainer();
}
throw new IllegalArgumentException(String.format("%d", type));
}
}
public static class TestSlave extends StructuredDataSlave<TestContainer, TestElement> {
public TestSlave(IStructureObserver<TestContainer, TestElement> observer) {
super(new TestFactory(), observer);
}
public TestSlave() {
super(new TestFactory());
}
@Override
protected void onConsistencyCheckFail() {
Assert.fail("Consistency check!");
}
public Map<Integer, TestContainer> getContainers() {
return containers;
}
public Map<Integer, TestElement> getElements() {
return elements;
}
}
private static StringTestContainer createStringContainer(final TestMaster master, String value) {
final StringTestContainer stringContainer = new StringTestContainer();
stringContainer.element.value = value;
master.addContainer(stringContainer);
return stringContainer;
}
private static IntTestContainer createIntContainer(final TestMaster master, int value) {
final IntTestContainer intContainer = new IntTestContainer();
intContainer.element.value = value;
master.addContainer(intContainer);
return intContainer;
}
private static void checkStringContainer(final Map<Integer, TestContainer> containers, int index, String value) {
final TestContainer c = containers.get(index);
Assert.assertTrue(c instanceof StringTestContainer);
final StringTestContainer stringC = (StringTestContainer)c;
Assert.assertEquals(value, stringC.element.value);
}
private static void checkIntContainer(final Map<Integer, TestContainer> containers, int index, int value) {
final TestContainer c = containers.get(index);
Assert.assertTrue(c instanceof IntTestContainer);
final IntTestContainer intC = (IntTestContainer)c;
Assert.assertEquals(value, intC.element.value);
}
private static void checkStringElement(final Map<Integer, TestElement> elements, int index, String value) {
final TestElement e = elements.get(index);
Assert.assertTrue(e instanceof StringTestElement);
final StringTestElement stringE = (StringTestElement)e;
Assert.assertEquals(value, stringE.value);
}
private static void checkIntElement(final Map<Integer, TestElement> elements, int index, int value) {
final TestElement e = elements.get(index);
Assert.assertTrue(e instanceof IntTestElement);
final IntTestElement intE = (IntTestElement)e;
Assert.assertEquals(value, intE.value);
}
private static void performUpdate(TestMaster master, TestSlave slave, Command.Type... types) {
final List<Command> commands = Lists.newArrayList();
master.appendUpdateCommands(commands);
Assert.assertEquals(types.length, commands.size());
for (int i = 0; i < types.length; i++)
checkCommandType(commands.get(i), types[i]);
slave.interpretCommandList(commands);
}
private static void checkCommandType(Command command, Type type) {
Assert.assertEquals(type, command.type());
}
@Test
public void testCreate() {
final IStructureObserver<TestContainer, TestElement> masterMock = createObserverMock();
final TestMaster master = new TestMaster(masterMock);
final IStructureObserver<TestContainer, TestElement> slaveMock = createObserverMock();
final TestSlave slave = new TestSlave(slaveMock);
final IntTestContainer intContainer = createIntContainer(master, 5);
final StringTestContainer stringContainer = createStringContainer(master, "world");
{
InOrder inOrder = inOrder(masterMock);
inOrder.verify(masterMock).onElementAdded(0, intContainer, 0, intContainer.element);
inOrder.verify(masterMock).onContainerAdded(0, intContainer);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verify(masterMock).onElementAdded(1, stringContainer, 1, stringContainer.element);
inOrder.verify(masterMock).onContainerAdded(1, stringContainer);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verifyNoMoreInteractions();
}
Assert.assertTrue(slave.isEmpty());
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
{
InOrder inOrder = inOrder(slaveMock);
inOrder.verify(slaveMock).onUpdateStarted();
inOrder.verify(slaveMock).onElementAdded(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onContainerAdded(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementAdded(eq(1), any(StringTestContainer.class), eq(1), any(StringTestElement.class));
inOrder.verify(slaveMock).onContainerAdded(eq(1), any(StringTestContainer.class));
inOrder.verify(slaveMock).onStructureUpdate();
inOrder.verify(slaveMock).onContainerUpdated(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementUpdated(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onContainerUpdated(eq(1), any(StringTestContainer.class));
inOrder.verify(slaveMock).onElementUpdated(eq(1), any(StringTestContainer.class), eq(1), any(StringTestElement.class));
inOrder.verify(slaveMock).onDataUpdate();
inOrder.verify(slaveMock).onUpdateFinished();
inOrder.verifyNoMoreInteractions();
}
Assert.assertFalse(slave.isEmpty());
{
final Map<Integer, TestContainer> containers = slave.getContainers();
Assert.assertEquals(2, containers.size());
checkIntContainer(containers, 0, 5);
checkStringContainer(containers, 1, "world");
}
{
final Map<Integer, TestElement> elements = slave.getElements();
Assert.assertEquals(2, elements.size());
checkIntElement(elements, 0, 5);
checkStringElement(elements, 1, "world");
}
}
@Test
public void testUpdate() {
final IStructureObserver<TestContainer, TestElement> masterMock = createObserverMock();
final TestMaster master = new TestMaster(masterMock);
final IStructureObserver<TestContainer, TestElement> slaveMock = createObserverMock();
final TestSlave slave = new TestSlave(slaveMock);
final IntTestContainer intContainer = createIntContainer(master, 5);
createStringContainer(master, "world");
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
reset((Object)masterMock, (Object)slaveMock);
intContainer.element.value = 42;
master.markElementModified(0);
{
InOrder inOrder = inOrder(masterMock);
inOrder.verify(masterMock).onContainerUpdated(0, intContainer);
inOrder.verify(masterMock).onElementUpdated(0, intContainer, 0, intContainer.element);
inOrder.verify(masterMock).onDataUpdate();
inOrder.verifyNoMoreInteractions();
}
performUpdate(master, slave, Command.Type.UPDATE_SINGLE);
{
InOrder inOrder = inOrder(slaveMock);
inOrder.verify(slaveMock).onUpdateStarted();
inOrder.verify(slaveMock).onContainerUpdated(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementUpdated(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onDataUpdate();
inOrder.verify(slaveMock).onUpdateFinished();
inOrder.verifyNoMoreInteractions();
}
{
final Map<Integer, TestContainer> containers = slave.getContainers();
Assert.assertEquals(2, containers.size());
checkIntContainer(containers, 0, 42);
checkStringContainer(containers, 1, "world");
}
{
final Map<Integer, TestElement> elements = slave.getElements();
Assert.assertEquals(2, elements.size());
checkIntElement(elements, 0, 42);
checkStringElement(elements, 1, "world");
}
}
@Test
public void testRemove() {
final IStructureObserver<TestContainer, TestElement> masterMock = createObserverMock();
final TestMaster master = new TestMaster(masterMock);
final IStructureObserver<TestContainer, TestElement> slaveMock = createObserverMock();
final TestSlave slave = new TestSlave(slaveMock);
final IntTestContainer intContainer = createIntContainer(master, 5);
createStringContainer(master, "world");
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
intContainer.element.value = 42;
master.markElementModified(0);
performUpdate(master, slave, Command.Type.UPDATE_SINGLE);
reset((Object)masterMock, (Object)slaveMock);
master.removeContainer(0);
{
InOrder inOrder = inOrder(masterMock);
inOrder.verify(masterMock).onContainerRemoved(0, intContainer);
inOrder.verify(masterMock).onElementRemoved(0, intContainer, 0, intContainer.element);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verifyNoMoreInteractions();
}
performUpdate(master, slave, Command.Type.DELETE, Command.Type.CONSISTENCY_CHECK);
{
InOrder inOrder = inOrder(slaveMock);
inOrder.verify(slaveMock).onUpdateStarted();
inOrder.verify(slaveMock).onContainerRemoved(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementRemoved(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onStructureUpdate();
inOrder.verify(slaveMock).onUpdateFinished();
inOrder.verifyNoMoreInteractions();
}
{
final Map<Integer, TestContainer> containers = slave.getContainers();
Assert.assertEquals(1, containers.size());
checkStringContainer(containers, 1, "world");
}
{
final Map<Integer, TestElement> elements = slave.getElements();
Assert.assertEquals(1, elements.size());
checkStringElement(elements, 1, "world");
}
}
@Test
public void testRemoveAllAndSlaveReset() {
final IStructureObserver<TestContainer, TestElement> masterMock = createObserverMock();
final TestMaster master = new TestMaster(masterMock);
final IStructureObserver<TestContainer, TestElement> slaveMock = createObserverMock();
final TestSlave slave = new TestSlave(slaveMock);
final IntTestContainer intContainer = createIntContainer(master, 5);
final StringTestContainer stringContainer = createStringContainer(master, "world");
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
reset((Object)masterMock, (Object)slaveMock);
master.removeAll();
{
InOrder inOrder = inOrder(masterMock);
inOrder.verify(masterMock).onContainerRemoved(0, intContainer);
inOrder.verify(masterMock).onElementRemoved(0, intContainer, 0, intContainer.element);
inOrder.verify(masterMock).onContainerRemoved(1, stringContainer);
inOrder.verify(masterMock).onElementRemoved(1, stringContainer, 1, stringContainer.element);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verifyNoMoreInteractions();
}
performUpdate(master, slave, Command.Type.RESET);
{
InOrder inOrder = inOrder(slaveMock);
inOrder.verify(slaveMock).onUpdateStarted();
inOrder.verify(slaveMock).onContainerRemoved(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementRemoved(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onContainerRemoved(eq(1), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementRemoved(eq(1), any(StringTestContainer.class), eq(1), any(StringTestElement.class));
inOrder.verify(slaveMock).onStructureUpdate();
inOrder.verify(slaveMock).onUpdateFinished();
inOrder.verifyNoMoreInteractions();
}
Assert.assertTrue(slave.getContainers().isEmpty());
Assert.assertTrue(slave.getElements().isEmpty());
}
@Test
public void testCreateAfterRemove() {
final IStructureObserver<TestContainer, TestElement> masterMock = createObserverMock();
final TestMaster master = new TestMaster(masterMock);
final IStructureObserver<TestContainer, TestElement> slaveMock = createObserverMock();
final TestSlave slave = new TestSlave(slaveMock);
final IntTestContainer intContainer = createIntContainer(master, 5);
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
reset((Object)masterMock, (Object)slaveMock);
master.removeAll();
final StringTestContainer stringContainer = createStringContainer(master, "world");
{
InOrder inOrder = inOrder(masterMock);
inOrder.verify(masterMock).onContainerRemoved(0, intContainer);
inOrder.verify(masterMock).onElementRemoved(0, intContainer, 0, intContainer.element);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verify(masterMock).onElementAdded(0, stringContainer, 0, stringContainer.element);
inOrder.verify(masterMock).onContainerAdded(0, stringContainer);
inOrder.verify(masterMock).onStructureUpdate();
inOrder.verifyNoMoreInteractions();
}
performUpdate(master, slave, Command.Type.RESET, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
{
InOrder inOrder = inOrder(slaveMock);
inOrder.verify(slaveMock).onUpdateStarted();
inOrder.verify(slaveMock).onContainerRemoved(eq(0), any(IntTestContainer.class));
inOrder.verify(slaveMock).onElementRemoved(eq(0), any(IntTestContainer.class), eq(0), any(IntTestElement.class));
inOrder.verify(slaveMock).onElementAdded(eq(0), any(StringTestContainer.class), eq(0), any(StringTestElement.class));
inOrder.verify(slaveMock).onContainerAdded(eq(0), any(StringTestContainer.class));
inOrder.verify(slaveMock).onStructureUpdate();
inOrder.verify(slaveMock).onContainerUpdated(eq(0), any(StringTestContainer.class));
inOrder.verify(slaveMock).onElementUpdated(eq(0), any(StringTestContainer.class), eq(0), any(StringTestElement.class));
inOrder.verify(slaveMock).onDataUpdate();
inOrder.verify(slaveMock).onUpdateFinished();
inOrder.verifyNoMoreInteractions();
}
{
final Map<Integer, TestContainer> containers = slave.getContainers();
Assert.assertEquals(1, containers.size());
checkStringContainer(containers, 0, "world");
}
{
final Map<Integer, TestElement> elements = slave.getElements();
Assert.assertEquals(1, elements.size());
checkStringElement(elements, 0, "world");
}
}
@SuppressWarnings("unchecked")
protected IStructureObserver<TestContainer, TestElement> createObserverMock() {
return mock(IStructureObserver.class);
}
@Test
public void testUpdateAfterCreate() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
final IntTestContainer intContainer = createIntContainer(master, 4);
intContainer.element.value = 9;
master.markElementModified(0);
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
checkIntContainer(slave.getContainers(), 0, 9);
}
@Test
public void testDeleteAfterCreate() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
createIntContainer(master, 6);
master.markElementModified(0);
master.removeContainer(0);
performUpdate(master, slave, Command.Type.CONSISTENCY_CHECK);
}
@Test
public void testDeleteAfterCreateAndUpdate() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
createIntContainer(master, 7);
master.removeContainer(0);
performUpdate(master, slave, Command.Type.CONSISTENCY_CHECK);
}
@Test
public void testCreateOnlyConsistency() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
int containerCount = 0;
createIntContainer(master, containerCount + 5);
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
checkIntContainer(slave.getContainers(), 0, containerCount + 5);
checkIntElement(slave.getElements(), 0, containerCount + 5);
containerCount++;
while (containerCount < StructuredDataMaster.CONSISTENCY_CHECK_PERIOD + 2) {
createIntContainer(master, containerCount + 5);
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
containerCount++;
{
final Map<Integer, TestContainer> containers = slave.getContainers();
final Map<Integer, TestElement> elements = slave.getElements();
for (int j = 0; j < containerCount; j++) {
checkIntContainer(containers, j, j + 5);
checkIntElement(elements, j, j + 5);
}
}
}
}
@Test
public void testCreateAndDeleteConsistency() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
int containerCount = 0;
createIntContainer(master, containerCount + 5);
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
checkIntContainer(slave.getContainers(), 0, containerCount + 5);
checkIntElement(slave.getElements(), 0, containerCount + 5);
containerCount++;
while (containerCount < StructuredDataMaster.CONSISTENCY_CHECK_PERIOD + 2) {
createIntContainer(master, containerCount + 5);
master.removeContainer(containerCount - 1);
performUpdate(master, slave, Command.Type.DELETE, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
{
final Map<Integer, TestContainer> containers = slave.getContainers();
Assert.assertEquals(1, containers.size());
final Map<Integer, TestElement> elements = slave.getElements();
Assert.assertEquals(1, elements.size());
checkIntContainer(containers, containerCount, containerCount + 5);
checkIntElement(elements, containerCount, containerCount + 5);
}
containerCount++;
}
}
@Test
public void testCustomCreateData() {
final TestMaster master = new TestMaster();
final TestSlave slave = new TestSlave();
final int c1IntValue = 53;
final String c1StringValue = "hello";
final long c1CustomValue = 4342;
{
final CustomDataTestContainer container = new CustomDataTestContainer();
container.intElement.value = c1IntValue;
container.stringElement.value = c1StringValue;
container.customValue = c1CustomValue;
master.addContainer(container);
}
final int c2IntValue = 523;
final String c2StringValue = "world";
final long c2CustomValue = -432;
{
final CustomDataTestContainer container = new CustomDataTestContainer();
container.intElement.value = c2IntValue;
container.stringElement.value = c2StringValue;
container.customValue = c2CustomValue;
master.addContainer(container);
}
performUpdate(master, slave, Command.Type.CREATE, Command.Type.CONSISTENCY_CHECK);
{
final Map<Integer, TestContainer> containers = slave.getContainers();
{
final TestContainer container = containers.get(0);
Assert.assertTrue(container instanceof CustomDataTestContainer);
final CustomDataTestContainer customContainer = (CustomDataTestContainer)container;
Assert.assertEquals(c1IntValue, customContainer.intElement.value);
Assert.assertEquals(c1StringValue, customContainer.stringElement.value);
Assert.assertEquals(c1CustomValue, customContainer.customValue);
}
{
final TestContainer container = containers.get(1);
Assert.assertTrue(container instanceof CustomDataTestContainer);
final CustomDataTestContainer customContainer = (CustomDataTestContainer)container;
Assert.assertEquals(c2IntValue, customContainer.intElement.value);
Assert.assertEquals(c2StringValue, customContainer.stringElement.value);
Assert.assertEquals(c2CustomValue, customContainer.customValue);
}
}
{
final Map<Integer, TestElement> elements = slave.getElements();
checkStringElement(elements, 0, c1StringValue);
checkIntElement(elements, 1, c1IntValue);
checkStringElement(elements, 2, c2StringValue);
checkIntElement(elements, 3, c2IntValue);
}
}
}