Package com.google.walkaround.slob.client

Source Code of com.google.walkaround.slob.client.GenericOperationChannelTest$RestrictedTimerService

/*
* Copyright 2011 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.walkaround.slob.client;

import com.google.walkaround.slob.client.ChannelTestUtil.Doc;
import com.google.walkaround.slob.client.ChannelTestUtil.FakeServer;
import com.google.walkaround.slob.client.ChannelTestUtil.SometimesCompactingTransformer;
import com.google.walkaround.slob.client.ChannelTestUtil.UnitPackage;
import com.google.walkaround.slob.client.GenericOperationChannel.Listener;
import com.google.walkaround.slob.client.GenericOperationChannel.ReceiveOpChannel;
import com.google.walkaround.slob.client.GenericOperationChannel.SendOpService;
import com.google.walkaround.slob.client.GenericOperationChannel.State;
import com.google.walkaround.util.client.log.Logs.Log;

import junit.framework.TestCase;

import org.waveprotocol.wave.client.scheduler.Scheduler;
import org.waveprotocol.wave.client.scheduler.Scheduler.Schedulable;
import org.waveprotocol.wave.client.scheduler.Scheduler.Task;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.util.DocOpScrub;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.testing.RandomDocOpGenerator;
import org.waveprotocol.wave.model.testing.RandomDocOpGenerator.Parameters;
import org.waveprotocol.wave.model.testing.RandomDocOpGenerator.RandomProvider;
import org.waveprotocol.wave.model.testing.RandomProviderImpl;
import org.waveprotocol.wave.model.util.FuzzingBackOffGenerator;

import java.util.List;

/**
* @author danilatos@google.com (Daniel Danilatos)
*/
public class GenericOperationChannelTest extends TestCase {

  /**
   * Verifies that only one task is scheduled at once.
   */
  class RestrictedTimerService extends StubTimerService {
    private Scheduler.Task currentTask;

    int expectedMinTimeLowerBound = 0;

    @Override
    public void schedule(Scheduler.Task task) {
      assertTrue(currentTask == null || currentTask == task);
      currentTask = task;
    }

    @Override
    public void scheduleDelayed(Task task, int minimumTime) {
      assert expectedMinTimeLowerBound != 0;
      assertNull(currentTask);
      assertTrue("Was: " + minimumTime,
          minimumTime >= expectedMinTimeLowerBound &&
          minimumTime < expectedMinTimeLowerBound * 2);

      currentTask = task;
    }

    @Override
    public boolean isScheduled(Schedulable job) {
      return currentTask == job;
    }

    @Override
    public void cancel(Schedulable job) {
      if (job == currentTask) {
        currentTask = null;
      }
    }

    void run() {
      assert hasTask();
      Scheduler.Task task = currentTask;
      currentTask = null;
      task.execute();
    }

    boolean hasTask() {
      return currentTask != null;
    }
  }

  /**
   * Verifies only one service request is made at a time (ignoring discarded
   * requests)
   */
  class RestrictedSendService implements SendOpService<DocOp> {
    private SendOpService.Callback oldCallback;
    private SendOpService.Callback callback;
    boolean opsWillReachServer = true;

    @Override
    public void requestRevision(SendOpService.Callback callback) {
      assertFalse(waiting());
      this.callback = callback;
      this.oldCallback = null;
    }

    @Override
    public void submitOperations(
        int revision, List<DocOp> operations, SendOpService.Callback callback) {
      assertFalse(waiting());
      this.callback = callback;
      this.oldCallback = null;

      if (opsWillReachServer) {
        server.sendToServer(revision, operations);
      }
    }

    void success(int version) {
      if (callback != null) {
        SendOpService.Callback tmp = callback;
        nullBoth();
        tmp.onSuccess(version);
      } else if (oldCallback != null) {
        oldCallback.onSuccess(version);
        oldCallback = null;
      }
    }

    void retryFailure() {
      if (callback != null) {
        SendOpService.Callback tmp = callback;
        nullBoth();
        tmp.onConnectionError(new Exception("dummy"));
      } else if (oldCallback != null) {
        oldCallback.onConnectionError(new Exception("dummy"));
        oldCallback = null;
      }
    }

    void nullBoth() {
      callback = null;
      oldCallback = null;
    }

    boolean waiting() {
      return callback != null;
    }

    @Override
    public void callbackNotNeeded(SendOpService.Callback callback) {
      assert callback == this.callback;
      this.oldCallback = this.callback;
      this.callback = null;
    }
  }


  FuzzingBackOffGenerator g = new FuzzingBackOffGenerator(1, 1000, 0);
  RestrictedTimerService timer = new RestrictedTimerService();

  ReceiveOpChannel.Listener<DocOp> channelListener;

  ReceiveOpChannel<DocOp> channel = new ReceiveOpChannel<DocOp>() {
    @Override public void connect(int revision, ReceiveOpChannel.Listener<DocOp> listener) {
      assert channelListener == null;
      channelListener = listener;
    }
    @Override public void disconnect() { }
  };
  RestrictedSendService service = new RestrictedSendService();

  private boolean notifiedClean = false;
  int operationsAcked = 0;
  int operationsReceived = 0;
  Listener<DocOp> listener = new Listener<DocOp>() {
    @Override public void onError(Throwable e) { }

    @Override public void onRemoteOp(DocOp dummy) {
      operationsReceived++;
    }

    @Override public void onAck(DocOp dummy, boolean clean) {
      assertFalse("notified clean twice in a row without becoming dirty in between", notifiedClean);
      notifiedClean = clean;
      operationsAcked++;
    }
  };


  RandomProvider r = RandomProviderImpl.ofSeed(7);
  Parameters p = new Parameters(); {
    p.setMaxOpeningComponents(5);
  }
  Doc clientDoc = new Doc();
  FakeServer server = new FakeServer(r, p);

  private final String clientSid = "me";
  private final String otherSid = "foo";
  private final SometimesCompactingTransformer transformer = new SometimesCompactingTransformer();

  GenericOperationChannel<DocOp> c = new GenericOperationChannel<DocOp>(
      g, timer, transformer, channel, service, listener, Log.DEV_NULL);

  @Override
  protected void setUp() throws Exception {
    try {
      assert false;
      fail("Assertions not turned on");
    } catch (AssertionError e) { }

    DocOpScrub.setShouldScrubByDefault(false);

    // Begin
    c.connect(0, clientSid);
  }

  public void testNormalAckXhrAckFirst() {
    sendOpsToServer(2, true);
    server.serverReceive();
    service.success(server.revision());
    // optimization should cause full ack before channel message
    assertEquals(0, operationsReceived);
    assertEquals(1, operationsAcked);
    assertTrue(c.isClean());

    channelMessages(1);
    assertEquals(0, operationsReceived);
    assertEquals(1, operationsAcked);
  }

  public void testNormalAckXhrAckFirstNotifiesCleanWithLastOpOnly() {
    transformer.compact = false;
    sendOpsToServer(2, true);
    server.serverReceive();
    service.success(server.revision());
    // optimization should cause full ack before channel message
    assertEquals(0, operationsReceived);
    assertEquals(2, operationsAcked);
    assertTrue(c.isClean());

    channelMessages(1);
    assertEquals(0, operationsReceived);
    assertEquals(2, operationsAcked);
  }

  public void testNormalAckChannelAckFirst() {
    sendOpsToServer(2, true);
    server.serverReceive();
    channelMessages(1);
    assertEquals(0, operationsReceived);
    assertEquals(1, operationsAcked);
    service.success(server.revision());
    assertEquals(0, operationsReceived);
    assertEquals(1, operationsAcked);
    assertTrue(c.isClean());
  }

  public void testNormalAckXhrAckFirstWithConcurrentOp() {
    sendOpsToServer(2, true);

    randomServerOps(1);
    server.serverReceive();

    service.success(server.revision());
    // optimization doesn't happen until channel message
    assertEquals(0, operationsAcked);
    assertFalse(c.isClean());

    channelMessages(2);
    assertEquals(1, operationsReceived);
    assertEquals(1, operationsAcked);
    assertTrue(c.isClean());
  }

  public void testNormalAckWithQueuedOps() {
    // Normal ack scenario, queued ops
    sendOpsToServer(2, true);
    queueMoreOps(3);
    server.serverReceive();
    service.success(server.revision());
    channelMessages(1);
    assertFalse(service.waiting());
    timer.run();
    assertTrue(service.waiting()); // sent next batch
    server.serverReceive();
    channelMessages(1);
    service.success(server.revision());
    assertTrue(c.isClean());
  }

  public void testSendWhileDelaying() {
    sendOpsToServer(2, false);
    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    localOps(1);
    timer.run();

    syncAndResendSuccessfully(1);
    assertEquals(State.ALL_ACKED, c.getState());

    timer.run();
    assertTrue(service.waiting()); // sent next batch
    server.serverReceive();
    channelMessages(1);
    service.success(server.revision());
    assertTrue(c.isClean());
  }

  public void testExponentialBackoffGrowsAndResets() {
    sendOpsToServer(2, false);

    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    assertEquals(State.DELAY_RESYNC, c.getState());
    timer.run();
    assertEquals(State.WAITING_SYNC, c.getState());

    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    assertEquals(State.DELAY_RESYNC, c.getState());
    timer.run();
    assertEquals(State.WAITING_SYNC, c.getState());

    timer.expectedMinTimeLowerBound = 2;
    service.retryFailure();
    assertEquals(State.DELAY_RESYNC, c.getState());
    timer.run();
    assertEquals(State.WAITING_SYNC, c.getState());

    timer.expectedMinTimeLowerBound = 3;
    service.retryFailure();
    assertEquals(State.DELAY_RESYNC, c.getState());
    timer.run();
    assertEquals(State.WAITING_SYNC, c.getState());

    syncAndResendSuccessfully(1);
    assertTrue(c.isClean()); // yay

    sendOpsToServer(2, false);

    timer.expectedMinTimeLowerBound = 1; // back off reset
    service.retryFailure();
    assertEquals(State.DELAY_RESYNC, c.getState());
    timer.run();
    assertEquals(State.WAITING_SYNC, c.getState());
  }

  private void syncAndResendSuccessfully(int num) {
    service.opsWillReachServer = true;
    service.success(0);
    assertTrue(service.waiting()); // retry immediately
    assertEquals(State.WAITING_ACK, c.getState());
    assert server.xhrInFlight();
    server.serverReceive();
    channelMessages(num);
    service.success(server.revision());
  }

  public void testChannelAckIgnoringXhrTemporaryFailure() {
    sendOpsToServer(2, true);
    server.serverReceive();
    channelMessages(1);
    service.retryFailure();
    assertTrue(c.isClean());
  }

  public void testHandleXhrFailureThenIgnoreAfterChannelAck() {
    sendOpsToServer(2, true);
    server.serverReceive();
    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    channelMessages(1);
    assertEquals(0, operationsReceived);
    assertTrue(c.isClean());
  }

  public void testXhrFailureHandledWaitOps() {
    // Xhr retriable failure handled, wait for ops to
    // come down after version sync xhr returns
    sendOpsToServer(2, false);
    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    randomServerOps(3);
    channelMessages(2);
    assertEquals(2, operationsReceived);
    timer.run();
    assertTrue(service.waiting());
    assertEquals(State.WAITING_SYNC, c.getState());
    service.success(c.revision() + 1);
    assertFalse(service.waiting()); // still have 1 more until version sync
    service.opsWillReachServer = true;
    channelMessages(1);
    assertEquals(3, operationsReceived);
    assertEquals(0, operationsAcked);
    assertTrue(service.waiting()); // now we've sent our retry
    server.serverReceive();
    channelMessages(1);
    assertEquals(3, operationsReceived);
    assertEquals(1, operationsAcked);
    assertTrue(c.isClean());
  }

  public void testXhrFailureHandledStraightAway() {
    // Xhr retriable failure handled, enough ops already
    // when xhr returns.
    sendOpsToServer(2, false);
    timer.expectedMinTimeLowerBound = 1;
    service.retryFailure();
    randomServerOps(2);
    channelMessages(2);
    assertEquals(2, operationsReceived);
    timer.run();
    assertTrue(service.waiting());
    assertEquals(State.WAITING_SYNC, c.getState());
    service.opsWillReachServer = true;
    service.success(c.revision());
    assertTrue(service.waiting()); // sent retry straight away
    randomServerOps(1);
    server.serverReceive();
    channelMessages(2);
    assertEquals(3, operationsReceived);
    assertTrue(c.isClean());
  }


  private void localOps(int num) {
    for (int i = 0; i < num; i++) {
      notifiedClean = false;
      c.send(randomOp(clientDoc));
    }
  }

  private void sendOpsToServer(int num, boolean success) {
    localOps(num);
    service.opsWillReachServer = success;
    timer.run();
    assertTrue(service.waiting());
    if (success) {
      assert server.xhrInFlight();
    }
  }

  private void queueMoreOps(int num) {
    localOps(num);
    if (timer.hasTask()) {
      timer.run();
    }
  }

  private void randomServerOps(int num) {
    for (int i = 0; i < num; i++) {
      server.randomServerOp();
    }
  }

  private void channelMessages(int num) {
    for (int i = 0; i < num; i++) {
      UnitPackage pkg = server.clientReceive();
      channelListener.onMessage(pkg.resultingRevision,
          pkg.clientSourced ? clientSid : otherSid, pkg.onlyOp());
    }
  }

  private DocOp randomOp(Doc doc) {
    DocOp op = RandomDocOpGenerator.generate(r, p, doc.autoDoc);
    try {
      doc.consume(op);
    } catch (OperationException e) {
      throw new RuntimeException(e);
    }
    return op;
  }
}
TOP

Related Classes of com.google.walkaround.slob.client.GenericOperationChannelTest$RestrictedTimerService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.