Package org.waveprotocol.wave.concurrencycontrol.client

Source Code of org.waveprotocol.wave.concurrencycontrol.client.OperationQueueTest

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.waveprotocol.wave.concurrencycontrol.client;

import junit.framework.TestCase;

import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder;
import org.waveprotocol.wave.model.operation.TransformException;
import org.waveprotocol.wave.model.operation.wave.BlipContentOperation;
import org.waveprotocol.wave.model.operation.wave.NoOp;
import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext;
import org.waveprotocol.wave.model.testing.DeltaTestUtil;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.wave.ParticipantId;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;

/**
* Tests {@link OperationQueue}.
*
*/

public class OperationQueueTest extends TestCase {
  private static final ParticipantId BOB = new ParticipantId("bob@example.com");
  private static final WaveletOperation BOB_A = new NoOp(makeContext(BOB, 1000));
  private static final WaveletOperation BOB_B = new NoOp(makeContext(BOB, 2000));
  private static final WaveletOperation BOB_C = new NoOp(makeContext(BOB, 3000));

  private static final ParticipantId JIM = new ParticipantId("jim@example.com");
  private static final WaveletOperation JIM_A = new NoOp(makeContext(JIM, 1000));
  private static final WaveletOperation JIM_B = new NoOp(makeContext(JIM, 2000));
  private static final WaveletOperation JIM_C = new NoOp(makeContext(JIM, 3000));

  private static final ParticipantId TOM = new ParticipantId("tom@example.com");
  private static final WaveletOperation TOM_B = new NoOp(makeContext(TOM, 2000));

  private OperationQueue queue;
  private MockDeltaTransformer transformer;

  @Override
  protected void setUp() {
    transformer = new MockDeltaTransformer();
    queue = new OperationQueue(transformer);
  }

  private static WaveletOperationContext makeContext(ParticipantId participant, long timestamp) {
    return new WaveletOperationContext(participant, timestamp, 1);
  }

  /** Tests that newly created queues are empty. */
  public void testQueueStartsEmpty() {
    assertQueueIsEmpty();
  }

  /**
   * Tests that adding operations to an empty queue results in it being
   * non-empty until those operations are taken out again.
   */
  public void testQueueNonemptyAfterOperationsAddedUntilTaken() {
    queue.add(BOB_A);
    assertQueueSize(1);
    queue.take();
    assertQueueIsEmpty();
  }

  /**
   * Tests that inserting a new head delta into an empty queue makes the queue
   * non-empty if and only if the delta has ops in it.
   */
  public void testQueueNonemptyAfterInsertingNonemptyHead() {
    queue.insertHead(new MergingSequence());
    assertQueueIsEmpty();
    MergingSequence head = new MergingSequence();
    head.add(BOB_A);
    queue.insertHead(head);
    assertQueueSize(1);
  }

  /**
   * Tests that attempting to take a delta from an empty queue results in a
   * {@link NoSuchElementException} being thrown.
   */
  public void testThrowsExceptionTakingFromEmptyQueue() {
    try {
      queue.take();
      fail("take() from empty queue should fail");
    } catch (NoSuchElementException expected) {
    }
  }

  /**
   * Tests that adding multiple consecutive operations from a single author
   * results in them being merged into the one delta.
   */
  public void testMergesConsecutiveOperationsWithSameCreator() {
    queue.add(BOB_A);
    queue.add(BOB_B);
    assertQueueSizeBetween(1, 2);
    assertEquals(list(BOB_A, BOB_B), copyList(queue.take()));
    // Queue should now be empty, even though we did two adds and only one take
    assertQueueIsEmpty();
  }

  /** Tests that operations by different authors are not merged. */
  public void testDoesNotMergeOperationsDividedByOtherAuthor() {
    queue.add(BOB_A);
    queue.add(JIM_B);
    assertQueueSizeBetween(1, 2);
    assertEquals(list(BOB_A), copyList(queue.take()));
    assertEquals(list(JIM_B), copyList(queue.take()));
  }

  /**
   * Tests that two operations from a single author that are added one after
   * another are not merged if the calls to add() are separated by a take()
   * which extracts the first operation.
   */
  public void testOperationsNotMergedOverQueueEmptyPoints() {
    queue.add(BOB_A);
    assertEquals(list(BOB_A), copyList(queue.take()));
    assertQueueIsEmpty();
    queue.add(BOB_B);
    assertEquals(list(BOB_B), copyList(queue.take()));
  }

  /**
   * Tests that two operations from a single author that are added one after
   * another are still merge if the calls to add() are separated by a take()
   * which extracts previously added operations rather than either of the two
   * aforementioned operations.
   */
  public void testOperationsMergeDespiteTakeIfQueueNotMadeEmpty() {
    queue.add(JIM_A);
    queue.add(BOB_B);
    assertEquals(list(JIM_A), copyList(queue.take()));
    assertQueueSize(1);
    assertQueueSizeBetween(1, 2);
    queue.add(BOB_C);
    assertEquals(list(BOB_B, BOB_C), copyList(queue.take()));
    assertQueueIsEmpty();
  }

  /**
   * Tests that a new head delta can be inserted in at the start of an empty
   * queue. Future adds of ops by the same creator do not merge with it.
   */
  public void testHeadInsertedIntoEmptyQueueAllowsFutureMerging() {
    MergingSequence head = new MergingSequence();
    head.add(BOB_A);
    head.add(BOB_B);
    queue.insertHead(head);
    queue.add(BOB_C);
    assertQueueSizeBetween(1, 3);
    // Delta pushed to head is not mergable to a single delta.
    assertEquals(list(BOB_A, BOB_B), copyList(queue.take()));
    assertEquals(list(BOB_C), copyList(queue.take()));
    assertTrue(queue.isEmpty());
  }

  /**
   * Tests that a new head delta can be inserted in a non-empty queue.
   * It does not merge with the existing head.
   */
  public void testInsertHeadMergesIfSameCreatorAsExistingHead() {
    queue.add(BOB_C);
    MergingSequence head = new MergingSequence();
    head.add(BOB_A);
    head.add(BOB_B);
    queue.insertHead(head);
    assertQueueSizeBetween(1, 3);

    // Delta pushed to head is not mergable to a single delta.
    assertEquals(list(BOB_A, BOB_B), copyList(queue.take()));
    assertEquals(list(BOB_C), copyList(queue.take()));
    assertTrue(queue.isEmpty());
  }

  /**
   * Tests that a new head delta which is being inserted does not merge with the
   * existing head if the creators do not match, hence the old head is moved to
   * be the second delta but otherwise remains unmodified.
   */
  public void testInsertHeadMakesNewDeltaIfCreatorDiffersFromExistingHead() {
    queue.add(BOB_C);
    MergingSequence head = new MergingSequence();
    head.add(JIM_A);
    head.add(JIM_B);
    queue.insertHead(head);
    assertEquals(list(JIM_A, JIM_B), copyList(queue.take()));
    assertEquals(list(BOB_C), copyList(queue.take()));
    assertTrue(queue.isEmpty());
  }

  /**
   * Tests that the deltas provided by take() are optimised. Note that this test
   * makes assumptions about what type of deltas are used by
   * {@link OperationQueue} and what operations those deltas merge.
   */
  public void testProducesOptimisedDeltas() {
    queue.add(new WaveletBlipOperation("a", new BlipContentOperation(BOB_A.getContext(),
        new DocOpBuilder().retain(1).characters("hi").retain(1).build())));
    queue.add(new WaveletBlipOperation("a", new BlipContentOperation(BOB_B.getContext(),
        new DocOpBuilder().retain(1).characters("hi").retain(3).build())));
    assertQueueSizeBetween(1, 2);
    assertEquals(1, queue.take().size());
    assertQueueIsEmpty();
  }

  /**
   * Tests that deltas which are transformed make their way back into the queue,
   * replacing the untransformed versions and being sent out of take().
   */
  public void testTransformedDeltasReplaceOriginals() throws TransformException {
    queue.add(BOB_A);
    queue.add(BOB_B);
    queue.add(JIM_C);

    transformer.expect(BOB_A, BOB_B).transformTo(BOB_A);
    transformer.expect(JIM_C).echo();

    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));
    transformer.checkDone();
    assertQueueSizeBetween(1, 2);
    assertEquals(list(BOB_A), copyList(queue.take()));
    assertEquals(list(JIM_C), copyList(queue.take()));
    assertQueueIsEmpty();
  }

  /**
   * Tests that deltas which become empty due to a transform are discarded from
   * the queue.
   */
  public void testDeltasEmptyAfterTransformAreDiscarded() throws TransformException {
    queue.add(BOB_A);
    queue.add(JIM_A);
    queue.add(BOB_B);
    queue.add(TOM_B);
    queue.add(BOB_C);

    transformer.expect(BOB_A).kill();
    transformer.expect(JIM_A).echo();
    transformer.expect(BOB_B).kill();
    transformer.expect(TOM_B).echo();
    transformer.expect(BOB_C).kill();

    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));
    transformer.checkDone();
    assertQueueSizeBetween(1, 2);
    assertEquals(list(JIM_A), copyList(queue.take()));
    assertEquals(list(TOM_B), copyList(queue.take()));
    assertQueueIsEmpty();
  }

  /**
   * Tests that deltas with the same author are merged if they end up being
   * consecutive after deltas which were previously dividing them are discarded
   * in a transform.
   */
  public void testAdjacentDeltasBySameAuthorAfterTransformDiscardsAreMerged()
      throws TransformException {
    queue.add(BOB_A);
    queue.add(JIM_A);
    queue.add(BOB_B);
    queue.add(TOM_B);
    queue.add(BOB_C);

    transformer.expect(BOB_A).echo();
    transformer.expect(JIM_A).kill();
    transformer.expect(BOB_B).echo();
    transformer.expect(TOM_B).kill();
    transformer.expect(BOB_C).echo();

    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));
    transformer.checkDone();
    assertQueueSizeBetween(1, 3);
    assertEquals(list(BOB_A, BOB_B, BOB_C), copyList(queue.take()));
    assertQueueIsEmpty();
  }

  /**
   * Test we still compose operations after we've transformed.
   */
  public void testComposistionAfterTransform() throws TransformException {
    DeltaTestUtil util = new DeltaTestUtil(BOB);

    // 2 ops merged into one.
    queue.add(util.noOpDocOp("blipA"));
    queue.add(util.noOpDocOp("blipA"));

    // Get a server op
    transformer.expect(util.noOpDocOp("blipA")).echo();
    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));

    // Adding one more operation after that should not merge into previously
    // transformed client op
    queue.add(util.noOpDocOp("blipA"));

    // This operation should merge into the previous op as the previous op is not
    // yet transformed
    queue.add(util.noOpDocOp("blipA"));

    // Should get 1 op, the result of merging and composing the two deltas.
    assertQueueSizeBetween(1, 3);
    assertEquals(list(util.noOpDocOp("blipA")), copyList(queue.take()));

    assertQueueIsEmpty();
  }

  public void testCompositionBeforeTransform() throws TransformException {
    DeltaTestUtil util = new DeltaTestUtil(BOB);

    // 2 ops merged into one.
    queue.add(util.noOpDocOp("blipA"));
    queue.add(util.noOpDocOp("blipA"));

    // Get a server op
    transformer.expect(util.noOpDocOp("blipA")).echo();
    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));

    // Should get only 1 op
    assertQueueSize(1);
    assertEquals(list(util.noOpDocOp("blipA")), copyList(queue.take()));

    assertQueueIsEmpty();
  }

  /**
   * Test we compose operations after we've transformed. However, we should get several
   * transformed client operations out in the same delta if they don't compose.
   */
  public void testGettingSeveralOpsInOneDelta() throws TransformException {
    DeltaTestUtil util = new DeltaTestUtil(BOB);

    // Do 1 client op
    queue.add(util.noOpDocOp("blipA"));

    // Get a server op
    transformer.expect(util.noOpDocOp("blipA")).echo();
    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));

    // Do another client op
    queue.add(util.noOpDocOp("blipB"));

    // Get a server op
    transformer.expect(util.noOpDocOp("blipA"), util.noOpDocOp("blipB")).echo();
    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));

    // Should get 2 ops in the same delta
    assertQueueSizeBetween(1, 2);
    assertEquals(list(util.noOpDocOp("blipA"), util.noOpDocOp("blipB")), copyList(queue.take()));

    assertQueueIsEmpty();
  }

  /**
   * Since transform and compose doesn't commute, test we don't compose operations after
   * we've sent the operation. So when we push to head, we shouldn't merge sent deltas.
   */
  public void testNoCompositionWithInsertHead() throws TransformException {
    DeltaTestUtil util = new DeltaTestUtil(BOB);

    // Do 1 client op
    queue.add(util.noOpDocOp("blipA"));
    // Pushing an op to the head should never merge
    queue.insertHead(Arrays.asList(util.noOpDocOp("blipA")));

    // Get a server op
    transformer.expect(util.noOpDocOp("blipA")).echo();
    transformer.expect(util.noOpDocOp("blipA")).echo();
    assertEquals(transformer.getOutputServerDelta(), // \u2620
        queue.transform(transformer.getInputServerDelta()));


    // Should be 2 deltas a the first one is not mergable
    assertQueueSizeBetween(1, 2);
    assertEquals(list(util.noOpDocOp("blipA")), copyList(queue.take()));
    assertEquals(list(util.noOpDocOp("blipA")), copyList(queue.take()));

    assertQueueIsEmpty();
  }

  /**
   * Test operations are actually transformed.
   */
  public void testOpsAreTransformed() throws TransformException {
    queue = new OperationQueue();
    DeltaTestUtil bob = new DeltaTestUtil(BOB);
    DeltaTestUtil jim = new DeltaTestUtil(JIM);

    // Do bob client op 1
    queue.add(bob.insert(1, "a", 1, null));

    // Get delta 1 from jim
    queue.transform(Arrays.asList(jim.insert(1, "j", 1, null)));

    // Do bob client op 2
    queue.add(bob.insert(1, "b", 3, null));

    // Get delta 2 from jim
    queue.transform(Arrays.asList(jim.insert(1, "i", 2, null)));

    // check ops are transformed
    assertEquals(list(bob.insert(1, "ba", 3, null)), copyList(queue.take()));

    assertQueueIsEmpty();
  }

  /**
   * Tests that the queue's size estimate is at least one per un-mergable delta.
   * Since it's an underestimate it must be exactly correct if each delta
   * contains one op.
   */
  public void testQueueSizeEstimateIsAtLeastDeltaSize() {
    queue.add(BOB_A);
    queue.add(JIM_A);
    queue.add(BOB_B);
    queue.add(JIM_B);
    queue.add(BOB_C);
    queue.add(JIM_C);
    assertEquals(6, queue.estimateSize());
  }

  /** Asserts that the queue is empty and size estimate is zero. */
  private void assertQueueIsEmpty() {
    assertTrue("Expected empty queue", queue.isEmpty());
    assertEquals("Expected queue size zero", 0, queue.estimateSize());
  }

  /**
   * Asserts that the queue is not empty and the size estimate is as expected.
   */
  private void assertQueueSize(int size) {
    assertFalse("Expected non-empty queue", queue.isEmpty());
    int estimate = queue.estimateSize();
    assertEquals("Expected queue size " + size + ", was " + estimate, size, estimate);
  }

  /**
   * Asserts that the queue is not empty and the size estimate is within
   * expected bounds (inclusive).
   */
  private void assertQueueSizeBetween(int minSize, int maxSize) {
    assertFalse("Expected non-empty queue", queue.isEmpty());
    int estimate = queue.estimateSize();
    assertTrue("Expected queue size >= " + minSize + ", was " + estimate, estimate >= minSize);
    assertTrue("Expected queue size <= " + maxSize + ", was " + estimate, estimate <= maxSize);
  }

  @SuppressWarnings("unchecked")
  private static <T> List<T> list(T... es) {
    return Collections.unmodifiableList(CollectionUtils.newArrayList(es));
  }

  private static <T> List<T> copyList(Iterable<T> es) {
    return Collections.unmodifiableList(CollectionUtils.newArrayList(es));
  }
}
TOP

Related Classes of org.waveprotocol.wave.concurrencycontrol.client.OperationQueueTest

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.