// Copyright 2012 Google 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.google.collide.client.collaboration; import static com.google.collide.client.collaboration.CollaborationTestUtils.*; import com.google.collide.shared.util.Reorderer.TimeoutCallback; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.client.Timer; /** * General tests for recovering from missed doc op scenarios. * * This mocks out a few classes, but purposefully does not mock out {@link DocOpReceiver} since it * is a critical component of recovery too. * */ public class DocOpRecovererTests extends GWTTestCase { private final TimeoutCallback finishTestTimeoutCallback = new TimeoutCallback() { @Override public void onTimeout(int lastVersionDispatched) { finishTest(); } }; /** * Non-collaborative session where a single user is typing and gets his acks out-of-order. */ public void testAcksOutOfOrderWithoutCollaborators() { final int startVersion = 1, docOpCount = 3; CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, FAIL_TIMEOUT_CALLBACK, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, docOpCount)); // Receives acks, but out-of-order o.transportSink.onDocOpReceived(newIncomingDocOpMsg(3), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); // Ensure doc op receiver is fed the right data assertEquals(startVersion + 3, o.receiverListener.revision()); } /** * Non-collaborative session where a single user is typing and his client doesn't get an ack. */ public void testAckNotReceivedWithoutCollaborators() { final int startVersion = 1, docOpCount = 3; CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, FAIL_TIMEOUT_CALLBACK, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, docOpCount)); // Imagine we never received an ack, trigger recovery o.api.expectAndReturn(newRecoverMsg(startVersion, docOpCount), newRecoverResponseMsg(startVersion + 1, docOpCount)); o.recoverer.recover(FAIL_ERROR_CALLBACK); // Ensure doc op receiver is fed the right data assertEquals(startVersion + 3, o.receiverListener.revision()); } /** * Non-collaborative session where a single user is typing and his client doesn't get an ack so he * does a recovery. However, after the recovery, the slow ack finally arrives, so make sure it * doesn't blow up because it's already seen the doc op by then. */ public void testAckReceivedAfterRecoveryWithoutCollaborators() { final int startVersion = 1, docOpCount = 3; CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, FAIL_TIMEOUT_CALLBACK, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, docOpCount)); // Imagine we never received an ack, trigger recovery o.api.expectAndReturn(newRecoverMsg(startVersion, docOpCount), newRecoverResponseMsg(startVersion + 1, docOpCount)); o.recoverer.recover(FAIL_ERROR_CALLBACK); // Ensure doc op receiver is fed the right data assertEquals(startVersion + 3, o.receiverListener.revision()); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(3), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); } /** * Collaborative session where a user is typing and his client doesn't get an ack. While he is * recovering, he gets collaborator doc ops. */ public void testAckNotReceivedWithCollaboratorsTypingDuringRecovery() { final int startVersion = 1, userDocOpCount = 3, collaboratorsDocOpCount = 6; final CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, FAIL_TIMEOUT_CALLBACK, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, userDocOpCount)); // Imagine we never received an ack, trigger recovery o.api.expectAndReturnAsync(newRecoverMsg(startVersion, userDocOpCount), newRecoverResponseMsg(2, userDocOpCount + collaboratorsDocOpCount)); o.recoverer.recover(FAIL_ERROR_CALLBACK); // Recover is waiting on XHR response, imagine the collaborator doc ops arrive right now o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(3), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(5), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(6), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(7), DOC_OP); delayTestFinish(100); new Timer() { @Override public void run() { // By now, the recovery XHR response should have come back // Ensure doc op receiver is fed the right data assertEquals( startVersion + userDocOpCount + collaboratorsDocOpCount, o.receiverListener.revision()); finishTest(); } }.schedule(1); } /** * Collaborative session where a user is typing and his client doesn't get an ack. While he is * recovering, he gets collaborator doc ops, but they are out-of-order. All of the collaborator * doc ops arrive in the recovery response (imagine they had been applied before the server got * the recovery XHR). */ public void testAckNotReceivedWithCollaboratorsTypingButOutOfOrderDuringRecovery() { final int startVersion = 1, userDocOpCount = 3, collaboratorsDocOpCount = 6; final CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, FAIL_TIMEOUT_CALLBACK, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, userDocOpCount)); // Imagine we never received an ack, trigger recovery o.api.expectAndReturnAsync(newRecoverMsg(startVersion, userDocOpCount), newRecoverResponseMsg(2, userDocOpCount + collaboratorsDocOpCount)); o.recoverer.recover(FAIL_ERROR_CALLBACK); // Recover is waiting on XHR response, imagine the collaborator doc ops arrive right now o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(5), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(7), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(3), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(6), DOC_OP); delayTestFinish(100); new Timer() { @Override public void run() { // By now, the recovery XHR response should have come back // Ensure doc op receiver is fed the right data assertEquals( startVersion + userDocOpCount + collaboratorsDocOpCount, o.receiverListener.revision()); finishTest(); } }.schedule(1); } /** * Non-collaborative session where a single user is typing and gets his acks out-of-order but is * missing one, so it times out. */ public void testAcksOutOfOrderAndTimesOutWithoutCollaborators() { final int startVersion = 1, docOpCount = 3; CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, finishTestTimeoutCallback, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, docOpCount)); // Receives acks, but out-of-order o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); delayTestFinish(100); } /** * Collaborative session where a user is typing and his client doesn't get an ack. While he is * recovering, he gets collaborator doc ops, but they are out-of-order. Some of the collaborator * doc ops were applied after the XHR got to the server, but suppose some weird network latency * existed so those arrived before the client got the XHR. One of those was dropped, so it should * timeout. */ public void testAckNotReceivedWithCollaboratorsTypingButOutOfOrderWithDropDuringRecovery() { final int startVersion = 1, userDocOpCount = 3, collaboratorsDocOpDuringXhrCount = 4; final CollaborationTestUtils.Objects o = CollaborationTestUtils.createObjects(startVersion, finishTestTimeoutCallback, 1); // User types something o.sender.set(newOutgoingDocOpMsg(startVersion, userDocOpCount)); // Imagine we never received an ack, trigger recovery o.api.expectAndReturnAsync(newRecoverMsg(startVersion, userDocOpCount), newRecoverResponseMsg(2, userDocOpCount + collaboratorsDocOpDuringXhrCount)); o.recoverer.recover(FAIL_ERROR_CALLBACK); // Recover is waiting on XHR response, imagine the collaborator doc ops arrive right now o.transportSink.onDocOpReceived(newIncomingDocOpMsg(2), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(5), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(3), DOC_OP); o.transportSink.onDocOpReceived(newIncomingDocOpMsg(4), DOC_OP); /* * Server gets XHR, it applies my 3 doc ops (v6, v7, v8), and also 2 more collaborator doc ops * (v9, v10). Imagine one of the collaborator doc ops is dropped (v9). Like we mentioned in * javadoc, client<->server has weird latency issues so the XHR response arrives later than the * collaborator doc ops. */ o.transportSink.onDocOpReceived(newIncomingDocOpMsg(10), DOC_OP); delayTestFinish(100); new Timer() { @Override public void run() { // By now, the recovery XHR response should have come back // Ensure doc op receiver is fed the right data assertTrue(startVersion + userDocOpCount + collaboratorsDocOpDuringXhrCount <= o.receiverListener.revision()); // We will now wait for the timeout (waiting for v6) to finish the test } }.schedule(1); } @Override public String getModuleName() { return "com.google.collide.client.collaboration.TestCollaboration"; } }