package lsr.paxos.replica; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import lsr.common.Reply; import lsr.common.Request; import lsr.common.RequestId; import lsr.common.SingleThreadDispatcher; import lsr.paxos.Snapshot; import lsr.service.Service; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; public class ServiceProxyTest { private SingleThreadDispatcher dispatcher; private Service service; private Map<Integer, List<Reply>> responsesCache; private ServiceProxy serviceProxy; @Before public void setUp() { dispatcher = mock(SingleThreadDispatcher.class); service = mock(Service.class); responsesCache = new HashMap<Integer, List<Reply>>(); serviceProxy = new ServiceProxy(service, responsesCache, dispatcher); } @Test public void shouldExecuteSingleRequestOnService() { Request request = new Request(new RequestId(0, 0), new byte[] {1, 2, 3}); serviceProxy.execute(request); verify(service).execute(new byte[] {1, 2, 3}, 0); } @Test public void shouldExecuteRequestsOnService() { Request request2 = new Request(new RequestId(2, 2), new byte[] {2}); Request request1 = new Request(new RequestId(1, 1), new byte[] {1}); serviceProxy.execute(request1); serviceProxy.instanceExecuted(0); serviceProxy.execute(request2); serviceProxy.instanceExecuted(1); verify(service).execute(new byte[] {1}, 0); verify(service).execute(new byte[] {2}, 1); } @Test public void shouldUpdateToSnapshot() { Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(7); snapshot.setNextInstanceId(2); snapshot.setStartingRequestSeqNo(5); snapshot.setValue(new byte[] {1, 2, 3}); snapshot.setPartialResponseCache(new ArrayList<Reply>()); serviceProxy.updateToSnapshot(snapshot); verify(service).updateToSnapshot(7, snapshot.getValue()); } @Test public void shouldSkipRequestsAfterUpdatingToSnapshot() { Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(7); snapshot.setNextInstanceId(2); snapshot.setStartingRequestSeqNo(5); snapshot.setValue(new byte[] {1, 2, 3}); Reply reply1 = new Reply(new RequestId(0, 0), new byte[] {1, 2, 3}); Reply reply2 = new Reply(new RequestId(1, 1), new byte[] {1, 2, 3, 4}); snapshot.setPartialResponseCache(Arrays.asList(reply1, reply2)); serviceProxy.updateToSnapshot(snapshot); verify(service).updateToSnapshot(7, snapshot.getValue()); byte[] skipped1 = serviceProxy.execute(RequestGenerator.generate()); byte[] skipped2 = serviceProxy.execute(RequestGenerator.generate()); assertArrayEquals(reply1.getValue(), skipped1); assertArrayEquals(reply2.getValue(), skipped2); verify(service, never()).execute(any(byte[].class), anyInt()); Request firstExecuted = RequestGenerator.generate(); serviceProxy.execute(firstExecuted); verify(service).execute(firstExecuted.getValue(), 7); } @Test public void shouldAskServiceForSnapshotWithoutPreviousSnapshot() { serviceProxy.askForSnapshot(); verify(service).askForSnapshot(-1); } @Test public void shouldAskServiceForSnapshotWithPreviousSnapshot() { Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(10); snapshot.setNextInstanceId(111); snapshot.setPartialResponseCache(new ArrayList<Reply>()); serviceProxy.updateToSnapshot(snapshot); serviceProxy.askForSnapshot(); verify(service).askForSnapshot(10); } @Test public void shouldForceSnapshotWithoutPreviousSnapshost() { serviceProxy.forceSnapshot(); verify(service).forceSnapshot(-1); } @Test public void shouldForceSnapshotWithPreviousSnapshot() { Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(10); snapshot.setNextInstanceId(111); snapshot.setPartialResponseCache(new ArrayList<Reply>()); serviceProxy.updateToSnapshot(snapshot); serviceProxy.forceSnapshot(); verify(service).forceSnapshot(10); } @Test public void shouldRecoveryFinished() { serviceProxy.recoveryFinished(); verify(service).recoveryFinished(); } @Test public void shouldRegisterSnapshotListener() { verify(service).addSnapshotListener(serviceProxy); } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionOnNullSnapshotValue() { serviceProxy.onSnapshotMade(1, null, null); executeDispatcher(); } @Test public void shouldHandleSnapshotFromService() { serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(0); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(1); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); responsesCache.put(2, Arrays.asList(ReplyGenerator.generate(), ReplyGenerator.generate(), ReplyGenerator.generate())); SnapshotListener2 snapshotListener2 = mock(SnapshotListener2.class); serviceProxy.addSnapshotListener(snapshotListener2); serviceProxy.onSnapshotMade(8, new byte[] {1, 2, 3}, null); executeDispatcher(); ArgumentCaptor<Snapshot> snapshotCaptor = ArgumentCaptor.forClass(Snapshot.class); verify(snapshotListener2).onSnapshotMade(snapshotCaptor.capture()); Snapshot snapshot = snapshotCaptor.getValue(); assertArrayEquals(new byte[] {1, 2, 3}, snapshot.getValue()); assertEquals(2, snapshot.getNextInstanceId()); assertEquals(8, snapshot.getNextRequestSeqNo()); assertEquals(5, snapshot.getStartingRequestSeqNo()); assertEquals(responsesCache.get(2), snapshot.getPartialResponseCache()); } @Test public void shouldHandleSnapshotFromServiceAfterInstanceIsExecuted() { serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(0); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(1); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(2); SnapshotListener2 snapshotListener2 = mock(SnapshotListener2.class); serviceProxy.addSnapshotListener(snapshotListener2); serviceProxy.onSnapshotMade(8, new byte[] {1, 2, 3}, null); executeDispatcher(); ArgumentCaptor<Snapshot> snapshotCaptor = ArgumentCaptor.forClass(Snapshot.class); verify(snapshotListener2).onSnapshotMade(snapshotCaptor.capture()); Snapshot snapshot = snapshotCaptor.getValue(); assertArrayEquals(new byte[] {1, 2, 3}, snapshot.getValue()); assertEquals(3, snapshot.getNextInstanceId()); assertEquals(8, snapshot.getNextRequestSeqNo()); assertEquals(8, snapshot.getStartingRequestSeqNo()); assertEquals(0, snapshot.getPartialResponseCache().size()); } @Test public void shouldHandleSnapshotFromServiceCalledWithinExecuteMethod() { serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(0); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(1); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); SnapshotListener2 snapshotListener2 = mock(SnapshotListener2.class); serviceProxy.addSnapshotListener(snapshotListener2); responsesCache.put(2, Arrays.asList(ReplyGenerator.generate(), ReplyGenerator.generate())); Request lastRequest = RequestGenerator.generate(); serviceProxy.execute(lastRequest); serviceProxy.onSnapshotMade(8, new byte[] {1, 2, 3}, new byte[] {1, 2, 3, 4}); executeDispatcher(); ArgumentCaptor<Snapshot> snapshotCaptor = ArgumentCaptor.forClass(Snapshot.class); verify(snapshotListener2).onSnapshotMade(snapshotCaptor.capture()); Snapshot snapshot = snapshotCaptor.getValue(); assertArrayEquals(new byte[] {1, 2, 3}, snapshot.getValue()); assertEquals(2, snapshot.getNextInstanceId()); assertEquals(8, snapshot.getNextRequestSeqNo()); assertEquals(5, snapshot.getStartingRequestSeqNo()); assertEquals(3, snapshot.getPartialResponseCache().size()); } @Test public void shouldHandleSnapshotFromPast() { serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(0); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(1); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(2); SnapshotListener2 snapshotListener2 = mock(SnapshotListener2.class); serviceProxy.addSnapshotListener(snapshotListener2); responsesCache.put(0, Arrays.asList(ReplyGenerator.generate(), ReplyGenerator.generate(), ReplyGenerator.generate())); serviceProxy.onSnapshotMade(1, new byte[] {1, 2, 3}, null); executeDispatcher(); ArgumentCaptor<Snapshot> snapshotCaptor = ArgumentCaptor.forClass(Snapshot.class); verify(snapshotListener2).onSnapshotMade(snapshotCaptor.capture()); Snapshot snapshot = snapshotCaptor.getValue(); assertArrayEquals(new byte[] {1, 2, 3}, snapshot.getValue()); assertEquals(0, snapshot.getNextInstanceId()); assertEquals(1, snapshot.getNextRequestSeqNo()); assertEquals(0, snapshot.getStartingRequestSeqNo()); assertEquals(1, snapshot.getPartialResponseCache().size()); } @Test public void shouldHandleSnapshotAfterUpdateToSnapshot() { SnapshotListener2 snapshotListener2 = mock(SnapshotListener2.class); serviceProxy.addSnapshotListener(snapshotListener2); Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(5); snapshot.setNextInstanceId(2); snapshot.setStartingRequestSeqNo(5); snapshot.setValue(new byte[] {1, 2, 3}); snapshot.setPartialResponseCache(new ArrayList<Reply>()); serviceProxy.updateToSnapshot(snapshot); serviceProxy.onSnapshotMade(5, new byte[] {1, 2, 3}, null); executeDispatcher(); ArgumentCaptor<Snapshot> snapshotCaptor = ArgumentCaptor.forClass(Snapshot.class); verify(snapshotListener2).onSnapshotMade(snapshotCaptor.capture()); Snapshot snapshot1 = snapshotCaptor.getValue(); assertArrayEquals(new byte[] {1, 2, 3}, snapshot1.getValue()); assertEquals(2, (int) snapshot1.getNextInstanceId()); assertEquals(5, snapshot1.getNextRequestSeqNo()); assertEquals(5, snapshot1.getStartingRequestSeqNo()); assertEquals(0, snapshot1.getPartialResponseCache().size()); } @Test public void shouldUpdateToSnapshotFromBeforeLastExecuted() { serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(0); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(1); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.execute(RequestGenerator.generate()); serviceProxy.instanceExecuted(2); Snapshot snapshot = new Snapshot(); snapshot.setNextRequestSeqNo(1); snapshot.setNextInstanceId(0); snapshot.setStartingRequestSeqNo(0); snapshot.setValue(new byte[] {1, 2, 3}); snapshot.setPartialResponseCache(new ArrayList<Reply>()); serviceProxy.updateToSnapshot(snapshot); verify(service).updateToSnapshot(1, snapshot.getValue()); } private void executeDispatcher() { ArgumentCaptor<Runnable> task = ArgumentCaptor.forClass(Runnable.class); verify(dispatcher).executeAndWait(task.capture()); task.getValue().run(); } } class ReplyGenerator { private static int id = 0; public static Reply generate() { id++; return new Reply(new RequestId(id, id), new byte[] {1, 2, 3}); } } class RequestGenerator { private static int id = 0; public static Request generate() { id++; return new Request(new RequestId(id, id), new byte[] {(byte) id}); } }