/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.wave.concurrencycontrol.wave; import org.waveprotocol.wave.concurrencycontrol.testing.MockOperationChannel; import junit.framework.TestCase; import org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory; import org.waveprotocol.wave.model.operation.wave.NoOp; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.wave.ParticipantId; import java.util.Queue; /** * Tests for operation sucker operation. * * @author anorth@google.com (Alex North) */ public class OperationSuckerTest extends TestCase { /** * A mock flushing operation sink. */ private static final class MockFlushingOperationSink implements FlushingOperationSink<WaveletOperation> { private static enum Method { CONSUME, FLUSH } private final Queue<Object[]> expectations = CollectionUtils.newLinkedList(); private Runnable resumeCommand; /** * Expects a call to consume an op. Optionally performs an action * when the consume() call is made. */ void expectConsume(WaveletOperation op, Runnable action) { expectations.add(new Object[] {Method.CONSUME, op, action}); } /** * Expects a call to flush and operation with a resume command. If {@code} * succeed is true then the call will return true, else it will return false * (and the caller will expect the resume command to be later invoked). */ void expectFlush(WaveletOperation operation, boolean succeed) { expectations.add(new Object[] {Method.FLUSH, operation, succeed}); } void checkExpectationsSatisfied() { assertTrue(expectations.isEmpty()); } /** * @return the last command passed to flush() */ Runnable getLastResumeCommand() { return resumeCommand; } @Override public void consume(WaveletOperation op) { Object[] expected = expectations.remove(); assertEquals(expected[0], Method.CONSUME); assertSame(expected[1], op); if (expected[2] != null) { ((Runnable) expected[2]).run(); } } @Override public boolean flush(WaveletOperation operation, Runnable resume) { Object[] expected = expectations.remove(); assertEquals(expected[0], Method.FLUSH); assertSame(expected[1], operation); resumeCommand = resume; return (Boolean) expected[2]; } } private final WaveletOperationContext.Factory contextFactory = new BasicWaveletOperationContextFactory(new ParticipantId("bob@example.com")); private MockOperationChannel channel; private MockFlushingOperationSink sink; private OperationSucker sucker; @Override protected void setUp() throws Exception { super.setUp(); channel = new MockOperationChannel(); sink = new MockFlushingOperationSink(); sucker = new OperationSucker(channel, sink); } @Override protected void tearDown() throws Exception { channel.checkExpectationsSatisfied(); sink.checkExpectationsSatisfied(); super.tearDown(); } /** * Tests that the sucker keeps sucking while ops are available and the * sink doesn't need to flush. */ public void testSucksWhileNoFlushNeeded() { // Set expectations. WaveletOperation[] ops = makeOps(3); for (int i = 0; i < 3; ++i) { WaveletOperation op = ops[0]; channel.expectPeek(op); sink.expectFlush(op, true); // The sucker peeks again after flush in case the op has changed. channel.expectPeek(op); channel.expectReceive(op); sink.expectConsume(op, null); } channel.expectPeek(null); // Go! sucker.onOperationReceived(); } /** * Tests that the sucker stops sucking if a flush is required, and * resumes when the flush is done. */ public void testFlushPausesSucking() { // Set expectations. WaveletOperation op = makeOp(); channel.expectPeek(op); sink.expectFlush(op, false); // Go! sucker.onOperationReceived(); channel.checkExpectationsSatisfied(); sink.checkExpectationsSatisfied(); Runnable resume = sink.getLastResumeCommand(); assertNotNull(resume); // Another op received should not cause any action sucker.onOperationReceived(); channel.checkExpectationsSatisfied(); sink.checkExpectationsSatisfied(); // Set expectations for the resume command. channel.expectPeek(op); sink.expectFlush(op, true); channel.expectPeek(op); channel.expectReceive(op); sink.expectConsume(op, null); channel.expectPeek(null); // Go! resume.run(); } public void testNoSuckingAfterShutdown() { sucker.shutdown(); sucker.onOperationReceived(); // Expect no interactions with the channel. } /** * Tests that the sucker doesn't touch the operation channel after being * shut down while consuming ops. */ public void testStopsSuckingAfterShutdown() { // Set expectations. WaveletOperation op = makeOp(); channel.expectPeek(op); sink.expectFlush(op, true); channel.expectPeek(op); channel.expectReceive(op); // Shut down the sucker when consuming the op. sink.expectConsume(op, new Runnable() { @Override public void run() { sucker.shutdown(); } }); // No more expectations of peek() or receive(). sucker.onOperationReceived(); } private WaveletOperation[] makeOps(int howMany) { WaveletOperation[] ops = new WaveletOperation[howMany]; for (int i = 0; i < howMany; ++i) { ops[i] = makeOp(); } return ops; } private WaveletOperation makeOp() { return new NoOp(contextFactory.createContext()); } }