http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/main/java/org/waveprotocol/wave/model/testing/WaveletDataFactory.java ---------------------------------------------------------------------- diff --git a/wave/src/main/java/org/waveprotocol/wave/model/testing/WaveletDataFactory.java b/wave/src/main/java/org/waveprotocol/wave/model/testing/WaveletDataFactory.java deleted file mode 100644 index bf8ff93..0000000 --- a/wave/src/main/java/org/waveprotocol/wave/model/testing/WaveletDataFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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.model.testing; - - -import org.waveprotocol.wave.model.id.IdGenerator; -import org.waveprotocol.wave.model.id.WaveId; -import org.waveprotocol.wave.model.id.WaveletId; -import org.waveprotocol.wave.model.version.HashedVersion; -import org.waveprotocol.wave.model.wave.ParticipantId; -import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; -import org.waveprotocol.wave.model.wave.data.WaveletData; -import org.waveprotocol.wave.model.wave.data.impl.EmptyWaveletSnapshot; - -/** - * Exposes any {@link ObservableWaveletData.Factory} as a {@link Factory}, by - * injecting suitable dependencies for testing. - * - */ -public final class WaveletDataFactory<T extends WaveletData> implements Factory<T> { - private final static WaveId WAVE_ID; - private final static WaveletId WAVELET_ID; - private static final ParticipantId PARTICIPANT_ID = new ParticipantId("[email protected]"); - - static { - IdGenerator gen = FakeIdGenerator.create(); - WAVE_ID = gen.newWaveId(); - WAVELET_ID = gen.newConversationWaveletId(); - } - - private final WaveletData.Factory<T> factory; - - private WaveletDataFactory(WaveletData.Factory<T> factory) { - this.factory = factory; - } - - public static <T extends WaveletData> Factory<T> of(WaveletData.Factory<T> factory) { - return new WaveletDataFactory<T>(factory); - } - - @Override - public T create() { - return factory.create(new EmptyWaveletSnapshot(WAVE_ID, WAVELET_ID, PARTICIPANT_ID, - HashedVersion.unsigned(0), 0)); - } -}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/box/server/robots/testing/OperationServiceHelper.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/box/server/robots/testing/OperationServiceHelper.java b/wave/src/test/java/org/waveprotocol/box/server/robots/testing/OperationServiceHelper.java new file mode 100644 index 0000000..814f972 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/box/server/robots/testing/OperationServiceHelper.java @@ -0,0 +1,120 @@ +/** + * 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.box.server.robots.testing; + +import static org.mockito.Mockito.mock; + +import com.google.wave.api.data.converter.EventDataConverter; +import com.google.wave.api.data.converter.v22.EventDataConverterV22; + +import org.waveprotocol.box.server.robots.OperationContextImpl; +import org.waveprotocol.box.server.robots.RobotWaveletData; +import org.waveprotocol.box.server.robots.operations.OperationService; +import org.waveprotocol.box.server.robots.util.ConversationUtil; +import org.waveprotocol.box.server.waveserver.WaveletProvider; +import org.waveprotocol.wave.model.conversation.ObservableConversation; +import org.waveprotocol.wave.model.conversation.WaveletBasedConversation; +import org.waveprotocol.wave.model.id.IdURIEncoderDecoder; +import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.model.operation.SilentOperationSink; +import org.waveprotocol.wave.model.operation.wave.BasicWaveletOperationContextFactory; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; +import org.waveprotocol.wave.model.testing.BasicFactories; +import org.waveprotocol.wave.model.testing.FakeIdGenerator; +import org.waveprotocol.wave.model.version.HashedVersionFactory; +import org.waveprotocol.wave.model.version.HashedVersionFactoryImpl; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.ParticipationHelper; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.DocumentOperationSink; +import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; +import org.waveprotocol.wave.model.wave.data.WaveletData; +import org.waveprotocol.wave.model.wave.data.impl.EmptyWaveletSnapshot; +import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.util.escapers.jvm.JavaUrlCodec; + +/** + * Helper for testing {@link OperationService}. Puts a single empty + * conversational wavelet with one participant in the operation context. + * + * @author [email protected] (Lennard de Rijk) + */ +public class OperationServiceHelper { + + private static final IdURIEncoderDecoder URI_CODEC = + new IdURIEncoderDecoder(new JavaUrlCodec()); + private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC); + private static final DocumentFactory<? extends DocumentOperationSink> DOCUMENT_FACTORY = + BasicFactories.observablePluggableMutableDocumentFactory(); + + private final WaveletProvider waveletProvider; + private final OperationContextImpl context; + + /** + * Constructs a new {@link OperationServiceHelper} with a wavelet with the + * name and participant that are passed in. + * + * @param waveletName the name of the empty wavelet to open in the context. + * @param participant the participant that should be on that empty wavelet. + */ + public OperationServiceHelper(WaveletName waveletName, ParticipantId participant) { + waveletProvider = mock(WaveletProvider.class); + EventDataConverter converter = new EventDataConverterV22(); + + ObservableWaveletData waveletData = WaveletDataImpl.Factory.create(DOCUMENT_FACTORY).create( + new EmptyWaveletSnapshot(waveletName.waveId, waveletName.waveletId, participant, + HASH_FACTORY.createVersionZero(waveletName), 0L)); + waveletData.addParticipant(participant); + + BasicWaveletOperationContextFactory CONTEXT_FACTORY = + new BasicWaveletOperationContextFactory(participant); + + SilentOperationSink<WaveletOperation> executor = + SilentOperationSink.Executor.<WaveletOperation, WaveletData>build(waveletData); + OpBasedWavelet wavelet = + new OpBasedWavelet(waveletData.getWaveId(), waveletData, CONTEXT_FACTORY, + ParticipationHelper.DEFAULT, executor, SilentOperationSink.VOID); + + // Make a conversation with an empty root blip + WaveletBasedConversation.makeWaveletConversational(wavelet); + ConversationUtil conversationUtil = new ConversationUtil(FakeIdGenerator.create()); + ObservableConversation conversation = conversationUtil.buildConversation(wavelet).getRoot(); + conversation.getRootThread().appendBlip(); + + context = new OperationContextImpl(waveletProvider, converter, conversationUtil); + context.putWavelet(waveletName.waveId, waveletName.waveletId, + new RobotWaveletData(waveletData, HASH_FACTORY.createVersionZero(waveletName))); + } + + /** + * @return the {@link WaveletProvider} mock + */ + public WaveletProvider getWaveletProvider() { + return waveletProvider; + } + + /** + * @return the {@link OperationContextImpl} with the empty wavelet opened. + */ + public OperationContextImpl getContext() { + return context; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java b/wave/src/test/java/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java new file mode 100644 index 0000000..fa45cd6 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/box/server/rpc/testing/FakeServerRpcController.java @@ -0,0 +1,84 @@ +/** + * 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.box.server.rpc.testing; + +import static org.waveprotocol.box.server.util.testing.TestingConstants.USER; + +import com.google.protobuf.RpcCallback; + +import org.waveprotocol.box.server.rpc.ServerRpcController; +import org.waveprotocol.wave.model.wave.ParticipantId; + + +/** + * An {@code RpcController} that just handles error text and failure condition. + */ +public class FakeServerRpcController implements ServerRpcController { + private boolean failed = false; + private String errorText = null; + + @Override + public String errorText() { + return errorText; + } + + @Override + public boolean failed() { + return failed; + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void notifyOnCancel(RpcCallback<Object> arg) { + } + + @Override + public void reset() { + failed = false; + errorText = null; + } + + @Override + public void setFailed(String error) { + failed = true; + errorText = error; + } + + @Override + public void startCancel() { + } + + @Override + public ParticipantId getLoggedInUser() { + return ParticipantId.ofUnsafe(USER); + } + + @Override + public void cancel() { + } + + @Override + public void run() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java b/wave/src/test/java/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java new file mode 100644 index 0000000..7ea3550 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/box/server/util/testing/ExceptionLogHandler.java @@ -0,0 +1,65 @@ +/** + * 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.box.server.util.testing; + +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * A log handler which throws a runtime exception at and above a set log level. + * Useful for catching erroneous conditions during testing, which are only + * reported to the logs. + * + * @author [email protected] (Michael Kuntzman) + */ +public class ExceptionLogHandler extends Handler { + /** The integer value of the minimum fatal log level. */ + private final int fatalLevel; + + /** + * Constructs an ExceptionLogHandler that throws a runtime exception at and above the specified + * log level. + * + * @param fatalLevel the minimum log level for which to throw an exception. + */ + public ExceptionLogHandler(Level fatalLevel) { + this.fatalLevel = fatalLevel.intValue(); + } + + /** + * @throws RuntimeException if the log record is at or above the fatal log level. + */ + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= fatalLevel) { + throw new RuntimeException(record.getLevel() + ": " + record.getMessage(), + record.getThrown()); + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/box/server/util/testing/Matchers.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/box/server/util/testing/Matchers.java b/wave/src/test/java/org/waveprotocol/box/server/util/testing/Matchers.java new file mode 100644 index 0000000..72be27a --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/box/server/util/testing/Matchers.java @@ -0,0 +1,172 @@ +/** + * 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.box.server.util.testing; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.Collection; + +/** + * Additional matchers to go with JUnit4's assertThat and assumeThat. + * + * @author [email protected] (Michael Kuntzman) + */ +// TODO(Michael): Maybe move this class to the libraries repository/branch. +public class Matchers { + /** + * Nicer aliases for some of the methods in this class, which may conflict with methods in other + * packages (potential conficts noted for each alias). + */ + public static class Aliases { + /** + * Alias for "containsString". May conflict with "org.mockito.Mockito.contains". + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> contains(final String substring) { + return containsString(substring); + } + + /** + * Alias for "matchesRegex". May conflict with "org.mockito.Mockito.matches". + * + * @param regularExpression to match against. + * @return a matcher for checking that a string matches the specified regular expression. + */ + public static TypeSafeMatcher<String> matches(final String regularExpression) { + return matchesRegex(regularExpression); + } + } + + /** + * A more user-friendly version of org.junit.matchers.JUnitMatchers.hasItem(T element). Allows a + * more verbose failure than assertTrue(collection.contains(item)). The matcher produces + * "Expected: a collection containing '...' got: '...'", whereas assertTrue produces merely + * "AssertionFailedError". + * Usage: static import, then assertThat(collection, contains(item)). + * + * @param item to look for. + * @return a matcher for checking that a collection contains the specified item. + */ + public static <T> TypeSafeMatcher<Collection<? super T>> contains(final T item) { + return new TypeSafeMatcher<Collection<? super T>>() { + @Override + public boolean matchesSafely(Collection<? super T> collection) { + return collection.contains(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("a collection containing ").appendValue(item); + } + }; + } + + /** + * Same as JUnitMatchers.containsString. Allows a more verbose failure than + * assertTrue(str.contains(substring)). + * Usage: static import, then assertThat(str, containsString(substring)). + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> containsString(final String substring) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return str.contains(substring); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string containing ").appendValue(substring); + } + }; + } + + /** + * The negative version of "contains" for a collection. Allows a more verbose failure than + * assertFalse(collection.contains(item)). + * Usage: static import, then assertThat(collection, doesNotContain(item)). + * + * @param item to look for. + * @return a matcher for checking that a collection does not contain the specified item. + */ + public static <T> TypeSafeMatcher<Collection<? super T>> doesNotContain(final T item) { + return new TypeSafeMatcher<Collection<? super T>>() { + @Override + public boolean matchesSafely(Collection<? super T> collection) { + return !collection.contains(item); + } + + @Override + public void describeTo(Description description) { + description.appendText("a collection NOT containing ").appendValue(item); + } + }; + } + + /** + * The negative version of "contains" for a string (or "containsString"). Allows a more verbose + * failure than assertFalse(str.contains(substring)). + * Usage: static import, then assertThat(str, doesNotContain(substring)). + * + * @param substring to look for. + * @return a matcher for checking that a string contains the specified substring. + */ + public static TypeSafeMatcher<String> doesNotContain(final String substring) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return !str.contains(substring); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string NOT containing ").appendValue(substring); + } + }; + } + + /** + * Allows a more verbose failure than assertTrue(str.matches(regex)). The matcher produces + * "Expected: a string matching regex '...' got: '...'", whereas assertTrue produces merely + * "AssertionFailedError". + * Usage: static import, then assertThat(str, matchesRegex(regex)). + * + * @param regularExpression to match against. + * @return a matcher for checking that a string matches the specified regular expression. + */ + public static TypeSafeMatcher<String> matchesRegex(final String regularExpression) { + return new TypeSafeMatcher<String>() { + @Override + public boolean matchesSafely(String str) { + return str.matches(regularExpression); + } + + @Override + public void describeTo(Description description) { + description.appendText("a string matching regex ").appendValue(regularExpression); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/box/server/util/testing/TestingConstants.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/box/server/util/testing/TestingConstants.java b/wave/src/test/java/org/waveprotocol/box/server/util/testing/TestingConstants.java new file mode 100644 index 0000000..ed0d95f --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/box/server/util/testing/TestingConstants.java @@ -0,0 +1,66 @@ +/** + * 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.box.server.util.testing; + +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.id.WaveletName; +import org.waveprotocol.wave.model.wave.ParticipantId; + +/** + * Commonly used constants for unit testing. Some constants taken from + * previously existing test cases. + * + * @author [email protected] (Michael Kuntzman) + */ +// TODO(Michael): Maybe move this class to the libraries repository/branch. +public interface TestingConstants { + public static final String BLIP_ID = "b+blip"; + + public static final String MESSAGE = "The quick brown fox jumps over the lazy dog"; + + public static final String MESSAGE2 = "Why's the rum gone?"; + + public static final String MESSAGE3 = "There is no spoon"; + + public static final String DOMAIN = "host.com"; + + public static final String OTHER_USER_NAME = "other"; + + public static final String OTHER_USER = OTHER_USER_NAME + "@" + DOMAIN; + + public static final ParticipantId OTHER_PARTICIPANT = new ParticipantId(OTHER_USER); + + public static final int PORT = 9876; + + public static final String USER_NAME = "user"; + + public static final String USER = USER_NAME + "@" + DOMAIN; + + public static final char[] PASSWORD = "password".toCharArray(); + + public static final ParticipantId PARTICIPANT = new ParticipantId(USER); + + public static final WaveId WAVE_ID = WaveId.of(DOMAIN, "w+wave"); + + public static final WaveletId WAVELET_ID = WaveletId.of(DOMAIN, "wavelet"); + + public static final WaveletName WAVELET_NAME = WaveletName.of(WAVE_ID, WAVELET_ID); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/client/testing/UndercurrentHarness.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/client/testing/UndercurrentHarness.java b/wave/src/test/java/org/waveprotocol/wave/client/testing/UndercurrentHarness.java new file mode 100644 index 0000000..48a6753 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/client/testing/UndercurrentHarness.java @@ -0,0 +1,380 @@ +/** + * 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.client.testing; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Command; + +import org.waveprotocol.wave.client.StageOne; +import org.waveprotocol.wave.client.StageThree; +import org.waveprotocol.wave.client.StageTwo; +import org.waveprotocol.wave.client.StageZero; +import org.waveprotocol.wave.client.Stages; +import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder; +import org.waveprotocol.wave.client.common.util.AsyncHolder; +import org.waveprotocol.wave.client.concurrencycontrol.MuxConnector; +import org.waveprotocol.wave.client.doodad.DoodadInstallers; +import org.waveprotocol.wave.client.doodad.attachment.AttachmentManagerProvider; +import org.waveprotocol.wave.client.doodad.attachment.testing.FakeAttachmentsManager; +import org.waveprotocol.wave.client.util.ClientFlags; +import org.waveprotocol.wave.client.util.NullTypedSource; +import org.waveprotocol.wave.client.util.OverridingTypedSource; +import org.waveprotocol.wave.client.wavepanel.impl.toolbar.color.ComplexColorPicker; +import org.waveprotocol.wave.client.wavepanel.impl.toolbar.color.SampleCustomColorPicker; +import org.waveprotocol.wave.common.bootstrap.FlagConstants; +import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService; +import org.waveprotocol.wave.model.conversation.Conversation; +import org.waveprotocol.wave.model.conversation.ConversationBlip; +import org.waveprotocol.wave.model.conversation.ConversationThread; +import org.waveprotocol.wave.model.conversation.ConversationView; +import org.waveprotocol.wave.model.conversation.WaveBasedConversationView; +import org.waveprotocol.wave.model.document.util.XmlStringBuilder; +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; +import org.waveprotocol.wave.model.testing.BasicFactories; +import org.waveprotocol.wave.model.testing.FakeIdGenerator; +import org.waveprotocol.wave.model.util.CollectionUtils; +import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV; +import org.waveprotocol.wave.model.util.StringMap; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; +import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; +import org.waveprotocol.wave.model.wave.data.WaveViewData; +import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl; +import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletConfigurator; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl.WaveletFactory; + +/** + * Kicks off some initial actions for development purposes. + * + */ +public class UndercurrentHarness implements EntryPoint { + + private UndercurrentHarness() { + } + + private static boolean loaded; + + /** + * Runs the harness script. + */ + @Override + public void onModuleLoad() { + AttachmentManagerProvider.init(new FakeAttachmentsManager()); + if (loaded) { + return; + } + loaded = true; + + final Timeline timeline = new Timeline(); + new Stages() { + + @Override + protected AsyncHolder<StageZero> createStageZeroLoader() { + return new StageZero.DefaultProvider() { + + @Override + protected void onStageInit() { + timeline.add("stage0_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage0_end"); + } + + @Override + protected UncaughtExceptionHandler createUncaughtExceptionHandler() { + return GWT.getUncaughtExceptionHandler(); + } + }; + } + + @Override + protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) { + return new StageOne.DefaultProvider(zero) { + @Override + protected void onStageInit() { + timeline.add("stage1_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage1_end"); + } + + @Override + protected Element createWaveHolder() { + return Document.get().getElementById("initialHtml"); + } + }; + } + + @Override + protected AsyncHolder<StageTwo> createStageTwoLoader(StageOne one) { + return new StageTwo.DefaultProvider(one, null) { + + @Override + protected void onStageInit() { + timeline.add("stage2_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage2_end"); + } + + @Override + protected void fetchWave(Accessor<WaveViewData> whenReady) { + timeline.add("fakewave_start"); + WaveViewData fake = WaveFactory.create(getDocumentRegistry()); + timeline.add("fakewave_end"); + whenReady.use(fake); + } + + @Override + protected ParticipantId createSignedInUser() { + return ParticipantId.ofUnsafe("[email protected]"); + } + + @Override + protected String createSessionId() { + return "session"; + } + + @Override + protected MuxConnector createConnector() { + return new MuxConnector() { + @Override + public void connect(Command whenOpened) { + if (whenOpened != null) { + whenOpened.execute(); + } + } + + @Override + public void close() { + // Ignore + } + }; + } + + @Override + protected WaveViewService createWaveViewService() { + // The vacuous MuxConnector should avoid the need for a + // communication layer. + throw new UnsupportedOperationException(); + } + + @Override + protected SchemaProvider createSchemas() { + return new ConversationSchemas(); + } + }; + } + + @Override + protected AsyncHolder<StageThree> createStageThreeLoader(StageTwo two) { + ClientFlags.resetWithSourceForTesting(OverridingTypedSource.of(new NullTypedSource()) + .withBoolean(FlagConstants.ENABLE_UNDERCURRENT_EDITING, true) + .build()); + + // Only for test additional color pickers + new SampleCustomColorPicker(ComplexColorPicker.getInstance()); + + return new StageThree.DefaultProvider(two) { + @Override + protected void onStageInit() { + timeline.add("stage3_start"); + } + + @Override + protected void onStageLoaded() { + timeline.add("stage3_end"); + } + }; + } + }.load(new Command() { + @Override + public void execute() { + showInfo(timeline); + } + }); + } + + /** + * Populates the info box. Continuously reports which element has browser + * focus, and reports timing information for the stage loading. + * + * @param timeline timeline to report + */ + private static void showInfo(Timeline timeline) { + Element timeBox = Document.get().getElementById("timeline"); + timeline.dump(timeBox); + + Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { + private final Element activeBox = Document.get().getElementById("active"); + + @Override + public boolean execute() { + Element e = getActiveElement(); + String text = (e != null ? e.getTagName() + " id:" + e.getId() : "none"); + activeBox.setInnerText(text); + return true; + } + + private native Element getActiveElement() /*-{ + return $doc.activeElement; + }-*/; + }, 1000); + } + + /** + * Creates a sample wave with a conversation in it. + */ + private final static class WaveFactory { + + /** + * Creates a sample wave. + * + * @param docFactory factory/registry for documents in the wave + * @return the wave state of the sample wave. + */ + public static WaveViewDataImpl create(DocumentFactory<?> docFactory) { + // Create a sample wave. + WaveViewData sampleData = createSampleWave(); + + // Now build one that has the same setup state as that required by + // undercurrent (complex issue with the per-document output sinks). + WaveViewDataImpl newData = WaveViewDataImpl.create(sampleData.getWaveId()); + WaveletDataImpl.Factory copier = WaveletDataImpl.Factory.create(docFactory); + for (ReadableWaveletData src : sampleData.getWavelets()) { + WaveletDataImpl copied = copier.create(src); + for (ParticipantId p : src.getParticipants()) { + copied.addParticipant(p); + } + copied.setVersion(copied.getVersion()); + copied.setHashedVersion(src.getHashedVersion()); + copied.setLastModifiedTime(src.getLastModifiedTime()); + newData.addWavelet(copied); + } + return newData; + } + + /** @return a sample wave with a conversation in it. */ + private static WaveViewData createSampleWave() { + final ParticipantId sampleAuthor = ParticipantId.ofUnsafe("[email protected]"); + IdGenerator gen = FakeIdGenerator.create(); + final WaveViewDataImpl waveData = WaveViewDataImpl.create(gen.newWaveId()); + final DocumentFactory<?> docFactory = BasicFactories.fakeDocumentFactory(); + final ObservableWaveletData.Factory<?> waveletDataFactory = + new ObservableWaveletData.Factory<WaveletDataImpl>() { + private final ObservableWaveletData.Factory<WaveletDataImpl> inner = + WaveletDataImpl.Factory.create(docFactory); + + @Override + public WaveletDataImpl create(ReadableWaveletData data) { + WaveletDataImpl wavelet = inner.create(data); + waveData.addWavelet(wavelet); + return wavelet; + } + }; + WaveletFactory<OpBasedWavelet> waveletFactory = BasicFactories + .opBasedWaveletFactoryBuilder() + .with(waveletDataFactory) + .with(sampleAuthor) + .build(); + + WaveViewImpl<?> wave = WaveViewImpl.create( + waveletFactory, waveData.getWaveId(), gen, sampleAuthor, WaveletConfigurator.ADD_CREATOR); + + // Build a conversation in that wave. + ConversationView v = WaveBasedConversationView.create(wave, gen); + Conversation c = v.createRoot(); + ConversationThread root = c.getRootThread(); + sampleReply(root.appendBlip()); + write(root.appendBlip()); + write(root.appendBlip()); + write(root.appendBlip()); + + return waveData; + } + + private static void write(ConversationBlip blip) { + org.waveprotocol.wave.model.document.Document d = blip.getContent(); + d.emptyElement(d.getDocumentElement()); + d.appendXml(XmlStringBuilder.createFromXmlString("<body><line></line>Hello World</body>")); + } + + private static void sampleReply(ConversationBlip blip) { + write(blip); + ConversationThread thread = blip.addReplyThread(8); + write(thread.appendBlip()); + } + + private static void biggerSampleReply(ConversationBlip blip) { + write(blip); + ConversationThread thread = blip.addReplyThread(); + sampleReply(thread.appendBlip()); + sampleReply(thread.appendBlip()); + write(thread.appendBlip()); + } + } + + private static class Timeline { + private final StringMap<Integer> events = CollectionUtils.createStringMap(); + private final Duration duration = new Duration(); + + void add(String name) { + events.put(name, duration.elapsedMillis()); + } + + void dump(Element timeBox) { + final SafeHtmlBuilder timeHtml = new SafeHtmlBuilder(); + timeHtml.appendHtmlConstant("<table cellpadding='0' cellspacing='0'>"); + events.each(new ProcV<Integer>() { + @Override + public void apply(String key, Integer value) { + timeHtml.appendHtmlConstant("<tr><td>"); + timeHtml.appendEscaped(key); + timeHtml.appendHtmlConstant(":</td><td>"); + timeHtml.appendEscaped("" + value); + timeHtml.appendHtmlConstant("</td></tr>"); + } + }); + timeHtml.appendHtmlConstant("</table>"); + timeBox.setInnerHTML(timeHtml.toSafeHtml().asString()); + + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html b/wave/src/test/java/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html new file mode 100644 index 0000000..a7ac1a3 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/client/testing/public/UndercurrentHarness.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<!-- + 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. +--> +<html> + <head> + <meta http-equiv="Content-type" content="text/html;charset=UTF-8"> + <meta name="gwt:property" content="locale=en"> + <title>Undercurrent Harness</title> + <style type='text/css'> + /* Full screen box. */ + body { + position: absolute; + margin: 0; + padding: 0; + left: 0; + right: 0; + top: 0; + bottom: 0; + + /* + * Arial seems to be the only non-ugly sans-serif font that renders roughly the same on both + * Chrome and Firefox. e.g., 'sans-serif' on Firefox is a nicer mystery font, because it is + * wider, but Chrome binds 'sans-serif' to 'arial'. + */ + font-family: arial; + font-size: small; + } + + /* Fixed-width and horizontally centered, in a way that works on IE. */ + #initialHtml { + position: absolute; + left: 50%; + right: 50%; + top: 4em; /* Enough space for the header. */ + bottom: 4em; + /* Expand out of the center line, aiming for ~70-75 character blip widths. */ + margin-left: -250px; + margin-right: -250px; + border: 1px solid lightGray; + } + + #info { + position: absolute; + z-index: 100; + right: 20px; + top: 20px; + padding: 0.2em; + width: 10em; + border: 1px solid black; + background-color: lightGray; + } + + #timeline { + margin-left: 1em; + } + + </style> + </head> + <body> + <h2>Undercurrent Test Harness</h2> + <div id='initialHtml'></div> + <div id='info'> + Active: <span id='active'></span><br/> + Timing: + <div id='timeline'></div> + </div> + <script type='text/javascript' src='waveharness.nocache.js'> + </script> + </body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/BlipTestUtils.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/BlipTestUtils.java b/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/BlipTestUtils.java new file mode 100644 index 0000000..4bdf453 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/BlipTestUtils.java @@ -0,0 +1,94 @@ +/** + * 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.model.conversation.testing; + +import org.waveprotocol.wave.model.conversation.Blips; +import org.waveprotocol.wave.model.conversation.ConversationBlip; +import org.waveprotocol.wave.model.conversation.TitleHelper; + +import org.waveprotocol.wave.model.document.ReadableWDocument; +import org.waveprotocol.wave.model.document.indexed.IndexedDocument; +import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; +import org.waveprotocol.wave.model.document.raw.impl.Element; +import org.waveprotocol.wave.model.document.raw.impl.Node; +import org.waveprotocol.wave.model.document.raw.impl.Text; +import org.waveprotocol.wave.model.document.util.DocHelper; +import org.waveprotocol.wave.model.document.util.DocProviders; +import org.waveprotocol.wave.model.document.util.LineContainers; +import org.waveprotocol.wave.model.document.util.XmlStringBuilder; + +/** + * Utility functions used by conversation tests. + * + */ +public final class BlipTestUtils { + + // Non-instantiatable class + private BlipTestUtils() { + } + + /** + * Returns the position of the body element in an initial, empty, blip. + */ + public static int getInitialBlipBodyPosition() { + IndexedDocument<Node, Element, Text> d = DocProviders.POJO.build( + TitleHelper.emptyDocumentWithTitle(), + DocumentSchema.NO_SCHEMA_CONSTRAINTS); + return getBodyPosition(d); + } + + + /** + * Returns the position of the body element of the given blip. + */ + public static int getBodyPosition(ConversationBlip blip) { + return getBodyPosition(blip.getContent()); + } + + /** + * Returns the position of the body element of the given document or 0 if the + * document has no body. + */ + public static <N, E extends N> int getBodyPosition(ReadableWDocument<N, E, ?> doc) { + N body = DocHelper.getElementWithTagName(doc, Blips.BODY_TAGNAME); + if (body == null) return 0; + return doc.getLocation(body); + } + + /** + * Wrap the given xml string containing a blip body in an XmlStringBuilder + * and prepend an empty head. + */ + public static XmlStringBuilder prependHead(String body) { + return XmlStringBuilder.createEmpty() + .append(Blips.INITIAL_HEAD) + .append(XmlStringBuilder.createFromXmlString(body)); + } + + /** + * Returns a blip whose contents are the given lines. + */ + public static String debugBlipWrap(String ... lines) { + return XmlStringBuilder.createEmpty() + .append(Blips.INITIAL_HEAD) + .append(XmlStringBuilder.createFromXmlString(LineContainers.debugContainerWrap(lines))) + .toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/FakeConversationView.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/FakeConversationView.java b/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/FakeConversationView.java new file mode 100644 index 0000000..85b2fd9 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/conversation/testing/FakeConversationView.java @@ -0,0 +1,159 @@ +/** + * 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.model.conversation.testing; + +import org.waveprotocol.wave.model.conversation.ObservableConversationView; +import org.waveprotocol.wave.model.conversation.WaveBasedConversationView; +import org.waveprotocol.wave.model.conversation.WaveletBasedConversation; +import org.waveprotocol.wave.model.id.IdGenerator; +import org.waveprotocol.wave.model.id.WaveId; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas; +import org.waveprotocol.wave.model.testing.FakeIdGenerator; +import org.waveprotocol.wave.model.testing.FakeWaveView; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.opbased.ObservableWaveView; + +import java.util.Collection; + +/** + * A fake conversation view. The view is fully functioning but not attached to + * any communication channels. + * + * @author [email protected] (Alex North) + */ +public final class FakeConversationView implements ObservableConversationView { + + private final static SchemaProvider DEFAULT_SCHEMAS = new ConversationSchemas(); + + public final static class Builder { + private SchemaProvider schemas; + private IdGenerator idGenerator; + private WaveId waveId; + private ParticipantId viewer; + private DocumentFactory<?> docFactory; + + private Builder() { + } + + public Builder with(DocumentFactory<?> docFactory) { + this.docFactory = docFactory; + return this; + } + + public Builder with(SchemaProvider schemas) { + this.schemas = schemas; + return this; + } + + public Builder with(IdGenerator idGenerator) { + this.idGenerator = idGenerator; + return this; + } + + public Builder with(WaveId wid) { + this.waveId = wid; + return this; + } + + public Builder with(ParticipantId viewer) { + this.viewer = viewer; + return this; + } + + public FakeConversationView build() { + if (schemas == null) { + schemas = DEFAULT_SCHEMAS; + } + if (idGenerator == null) { + idGenerator = FakeIdGenerator.create(); + } + if (waveId == null) { + waveId = idGenerator.newWaveId(); + } + + FakeWaveView waveView = FakeWaveView.builder(schemas) // \u2620 + .with(docFactory) // \u2620 + .with(idGenerator) // \u2620 + .with(waveId) // \u2620 + .with(viewer) // \u2620 + .build(); + + return new FakeConversationView(WaveBasedConversationView.create(waveView, idGenerator)); + } + } + + /** Creates a new conversation view builder. */ + public static Builder builder() { + return new Builder(); + } + + /** The backing conversation view. */ + private final WaveBasedConversationView view; + + private FakeConversationView(WaveBasedConversationView view) { + this.view = view; + } + + @Override + public String getId() { + return view.getId(); + } + + @Override + public WaveletBasedConversation createConversation() { + return view.createConversation(); + } + + @Override + public WaveletBasedConversation createRoot() { + return view.createRoot(); + } + + @Override + public WaveletBasedConversation getConversation(String conversationId) { + return view.getConversation(conversationId); + } + + @Override + public Collection<? extends WaveletBasedConversation> getConversations() { + return view.getConversations(); + } + + @Override + public WaveletBasedConversation getRoot() { + return view.getRoot(); + } + + @Override + public void addListener(Listener listener) { + view.addListener(listener); + } + + @Override + public void removeListener(Listener listener) { + view.removeListener(listener); + } + + public ObservableWaveView getWaveView() { + return view.getWaveView(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/BasicFactories.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/BasicFactories.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/BasicFactories.java new file mode 100644 index 0000000..f23e93b --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/BasicFactories.java @@ -0,0 +1,239 @@ +/** + * 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.model.testing; + +import org.waveprotocol.wave.model.document.Document; +import org.waveprotocol.wave.model.document.ObservableDocument; +import org.waveprotocol.wave.model.document.indexed.IndexedDocument; +import org.waveprotocol.wave.model.document.operation.DocInitialization; +import org.waveprotocol.wave.model.document.operation.DocOp; +import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; +import org.waveprotocol.wave.model.document.raw.impl.Element; +import org.waveprotocol.wave.model.document.raw.impl.Node; +import org.waveprotocol.wave.model.document.raw.impl.Text; +import org.waveprotocol.wave.model.document.util.DocProviders; +import org.waveprotocol.wave.model.document.util.DocumentImpl; +import org.waveprotocol.wave.model.document.util.DocumentProvider; +import org.waveprotocol.wave.model.operation.SilentOperationSink; +import org.waveprotocol.wave.model.schema.SchemaCollection; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.util.Preconditions; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.MuteDocumentFactory; +import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument; +import org.waveprotocol.wave.model.wave.data.impl.PluggableMutableDocument; +import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; + +import java.util.Map; + +/** + * Static factory for creating various document factories and builders whose + * document schemas come from the provider set using + * {@link #setSchemaProvider(SchemaProvider)}. If no provider is set the empty + * provider is used. This should only be used for tests of the core model. + * + */ +public class BasicFactories { + + /** + * Provider of {@link Document}s based on the {@link DocProviders#POJO} DOM + * implementation. + */ + private static final DocumentProvider<Document> DOC_PROVIDER = new DocumentProvider<Document>() { + @Override + public Document create(String tagName, Map<String, String> attributes) { + IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.create(tagName, attributes); + return new DocumentImpl(DocProviders.createTrivialSequencer(doc), doc); + } + + @Override + public Document parse(String text) { + IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse(text); + return new DocumentImpl(DocProviders.createTrivialSequencer(doc), doc); + } + }; + + /** + * Provider of {@link ObservableDocument}s based on the + * {@link DocProviders#POJO} DOM implementation and a trivial sequence. + */ + private static final DocumentProvider<ObservablePluggableMutableDocument> OBS_DOC_PROVIDER = + new DocumentProvider<ObservablePluggableMutableDocument>() { + @Override + public ObservablePluggableMutableDocument create( + String tagName, Map<String, String> attributes) { + // FIXME(ohler): this is inefficient. + return build(DocProviders.POJO.create(tagName, attributes).asOperation()); + } + + @Override + public ObservablePluggableMutableDocument parse(String text) { + // FIXME(ohler): this is inefficient. + return build(DocProviders.POJO.parse(text).asOperation()); + } + + private ObservablePluggableMutableDocument build(DocInitialization init) { + ObservablePluggableMutableDocument doc = + new ObservablePluggableMutableDocument(DocumentSchema.NO_SCHEMA_CONSTRAINTS, init); + doc.init(SilentOperationSink.VOID); + return doc; + } + }; + + private static SchemaProvider schemas = SchemaCollection.empty(); + + /** + * Sets the schema provider that will provide schemas for the factories + * returned from the methods of this class. + */ + public static void setSchemaProvider(SchemaProvider value) { + schemas = value; + } + + /** + * Returns the current schema provider. + */ + protected static SchemaProvider getSchemas() { + return schemas; + } + + /** + * Returns a new fake wave view builder whose document schemas comes from the + * current provider. + */ + public static FakeWaveView.Builder fakeWaveViewBuilder() { + return FakeWaveView.builder(getSchemas()); + } + + /** + * Returns a new op-based wavelet factory builder whose document schemas comes + * from the current provider. + */ + public static OpBasedWaveletFactory.Builder opBasedWaveletFactoryBuilder() { + return OpBasedWaveletFactory.builder(getSchemas()); + } + + /** + * Returns a new wavelet data impl factory whose document schemas comes from + * the current provider. + */ + public static WaveletDataImpl.Factory waveletDataImplFactory() { + return WaveletDataImpl.Factory.create(observablePluggableMutableDocumentFactory()); + } + + /** + * Returns a mute document factory whose document schemas comes from the + * current provider. + */ + public static MuteDocumentFactory muteDocumentFactory() { + return new MuteDocumentFactory(getSchemas()); + } + + /** + * Returns a fake document factory whose document schemas comes from the + * current provider. + */ + public static FakeDocument.Factory fakeDocumentFactory() { + return FakeDocument.Factory.create(getSchemas()); + } + + /** + * Returns a plugable mutable document factory whose document schemas comes + * from the current provider. + */ + public static DocumentFactory<? extends PluggableMutableDocument> + pluggableMutableDocumentFactory() { + return PluggableMutableDocument.createFactory(getSchemas()); + } + + /** + * Returns an observable pluggable mutable document factory whose document + * schemas comes from the current provider. + */ + public static DocumentFactory<? extends ObservablePluggableMutableDocument> + observablePluggableMutableDocumentFactory() { + return ObservablePluggableMutableDocument.createFactory(getSchemas()); + } + + /** + * Returns a provider of {@link Document}s. + * + * Provided documents have no schema constraints: consider using + * {@link MuteDocumentFactory} instead. + * + * TODO(anorth): Remove this method in favor of one specifying a schema. + */ + public static DocumentProvider<Document> documentProvider() { + return DOC_PROVIDER; + } + + /** + * Returns a provider of observable mutable documents. + * + * Provided documents have no schema constraints: consider using + * {@link MuteDocumentFactory} instead. + * + * TODO(anorth): Change generic type to ObservableDocument after fixing + * callers. + * + * TODO(anorth): Remove this method in favor of one specifying a schema. + */ + public static DocumentProvider<ObservablePluggableMutableDocument> observableDocumentProvider() { + return OBS_DOC_PROVIDER; + } + + /** + * Creates an observable mutable document with some schema, content, and sink. + */ + public static ObservableDocument createDocument(DocumentSchema schema, + String initialContent, SilentOperationSink<? super DocOp> sink) { + Preconditions.checkNotNull(sink, "Sink can't be null"); + DocInitialization init = DocProviders.POJO.parse(initialContent).asOperation(); + ObservablePluggableMutableDocument doc = new ObservablePluggableMutableDocument(schema, init); + doc.init(sink); + return doc; + } + + /** + * Creates an observable mutable document with some schema and a sink. + */ + public static ObservableDocument createDocument( + DocumentSchema schema, SilentOperationSink<? super DocOp> sink) { + return createDocument(schema, "", sink); + } + + /** + * Creates an observable mutable document with some schema. + */ + public static ObservableDocument createDocument(DocumentSchema schema) { + return createDocument(schema, "", SilentOperationSink.VOID); + } + + /** + * Creates an observable mutable document with some schema and initial content + */ + public static ObservableDocument createDocument( + DocumentSchema schema, String initialContent) { + return createDocument(schema, initialContent, SilentOperationSink.VOID); + } + + protected BasicFactories() { + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/DeltaTestUtil.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/DeltaTestUtil.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/DeltaTestUtil.java new file mode 100755 index 0000000..2bd7021 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/DeltaTestUtil.java @@ -0,0 +1,199 @@ +/** + * 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.model.testing; + +import org.waveprotocol.wave.model.document.operation.DocOp; +import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder; +import org.waveprotocol.wave.model.operation.wave.AddParticipant; +import org.waveprotocol.wave.model.operation.wave.BlipContentOperation; +import org.waveprotocol.wave.model.operation.wave.NoOp; +import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; +import org.waveprotocol.wave.model.operation.wave.TransformedWaveletDelta; +import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; +import org.waveprotocol.wave.model.operation.wave.WaveletDelta; +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.version.HashedVersion; +import org.waveprotocol.wave.model.wave.Constants; +import org.waveprotocol.wave.model.wave.ParticipantId; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + + +/** + * A bunch of utility functions to make testing easier. + * + * @author [email protected] (David Wang) + */ +public class DeltaTestUtil { + private static final WaveletOperationContext DUMMY = new WaveletOperationContext(null, 0L, 0L); + + private final ParticipantId author; + private final Random random = new Random(42); + + /** + * Creates a {@link DeltaTestUtil} with which operations authored by the given + * author can readily be made. + */ + public DeltaTestUtil(String author) { + this(new ParticipantId(author)); + } + + /** + * Creates a {@link DeltaTestUtil} with which operations authored by the given + * author can readily be made. + */ + public DeltaTestUtil(ParticipantId author) { + this.author = author; + } + + public ParticipantId getAuthor() { + return author; + } + + /** + * Creates an XmlDelete with the given data. + */ + public WaveletOperation delete(int posStart, String characters, int remaining) { + DocOp op = new DocOpBuilder() + .retain(posStart) + .deleteCharacters(characters) + .retain(remaining) + .build(); + BlipContentOperation blipOp = new BlipContentOperation( + new WaveletOperationContext(author, 0L, 1), op); + WaveletBlipOperation waveOp = new WaveletBlipOperation("blip id", blipOp); + return waveOp; + } + + /** + * Wrap an op with a delta. + */ + public TransformedWaveletDelta delta(long targetVersion, WaveletOperation op) { + return TransformedWaveletDelta.cloneOperations(author, + HashedVersion.unsigned(targetVersion + 1), 0L, Arrays.asList(op)); + } + + /** + * Create a delta with a single NoOp operation. + * + * @param initialVersion The version before the operation. + */ + public TransformedWaveletDelta noOpDelta(long initialVersion) { + return makeTransformedDelta(0L, HashedVersion.unsigned(initialVersion + 1), 1); + } + + /** Create a NoOp operation. */ + public NoOp noOp() { + return new NoOp(new WaveletOperationContext(author, 0L, 1L)); + } + + /** Create an AddParticipant operation. */ + public AddParticipant addParticipant(ParticipantId participant) { + return new AddParticipant(new WaveletOperationContext(author, 0L, 1L), participant); + } + + /** Creates a RemoveParticipant operation. */ + public RemoveParticipant removeParticipant(ParticipantId participant) { + return new RemoveParticipant(new WaveletOperationContext(author, 0L, 1L), participant); + } + + /** + * A docop that is empty. i.e. does nothing to the document. The document must + * also be empty, otherwise the operation is invalid. + */ + public WaveletOperation noOpDocOp(String blipId) { + WaveletOperationContext context = new WaveletOperationContext(author, 0L, 1L); + BlipContentOperation blipOp = new BlipContentOperation(context, (new DocOpBuilder()).build()); + return new WaveletBlipOperation(blipId, blipOp); + } + + /** + * Creates an XmlInsert with the given data. + */ + public WaveletOperation insert(int pos, String text, int remaining, + HashedVersion resultingVersion) { + DocOpBuilder builder = new DocOpBuilder(); + builder.retain(pos).characters(text); + if (remaining > 0) { + builder.retain(remaining); + } + BlipContentOperation blipOp = new BlipContentOperation( + new WaveletOperationContext(author, 0L, 1, resultingVersion), builder.build()); + WaveletBlipOperation waveOp = new WaveletBlipOperation("blip id", blipOp); + return waveOp; + } + + /** + * Builds a random client delta. + */ + public WaveletDelta makeDelta(HashedVersion targetVersion, long timestamp, int numOps) { + List<WaveletOperation> ops = CollectionUtils.newArrayList(); + WaveletOperationContext context = + new WaveletOperationContext(author, Constants.NO_TIMESTAMP, 1); + for (int i = 0; i < numOps; ++i) { + ops.add(randomOp(context)); + } + return new WaveletDelta(author, targetVersion, ops); + } + + /** + * Builds a no-op client delta. + */ + public WaveletDelta makeNoOpDelta(HashedVersion targetVersion, long timestamp, int numOps) { + List<WaveletOperation> ops = CollectionUtils.newArrayList(); + WaveletOperationContext context = + new WaveletOperationContext(author, Constants.NO_TIMESTAMP, 1); + for (int i = 0; i < numOps; ++i) { + ops.add(new NoOp(context)); + } + return new WaveletDelta(author, targetVersion, ops); + } + + /** + * Builds a random transformed delta. + */ + public TransformedWaveletDelta makeTransformedDelta(long applicationTimestamp, + HashedVersion resultingVersion, int numOps) { + List<WaveletOperation> ops = CollectionUtils.newArrayList(); + for (int i = 0; i < numOps; ++i) { + ops.add(randomOp(DUMMY)); + } + return TransformedWaveletDelta.cloneOperations(author, resultingVersion, applicationTimestamp, + ops); + } + + /** + * Creates a random op. The result is unlikely to be applicable to any + * wavelet, but is generated such that we are fairly certain that it will be + * unique so we can identify it when it completes a round-trip. + */ + private WaveletOperation randomOp(WaveletOperationContext context) { + DocOp blipOp = new DocOpBuilder() + .retain(Math.abs(random.nextInt()) / 2 + 1) + .characters("createRndOp#" + random.nextInt()) + .build(); + return new WaveletBlipOperation("createRndId#" + random.nextInt(), + new BlipContentOperation(context, blipOp)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/DocOpCreator.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/DocOpCreator.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/DocOpCreator.java new file mode 100644 index 0000000..31f589b --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/DocOpCreator.java @@ -0,0 +1,266 @@ +/** + * 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.model.testing; + +import org.waveprotocol.wave.model.document.operation.Attributes; +import org.waveprotocol.wave.model.document.operation.AttributesUpdate; +import org.waveprotocol.wave.model.document.operation.DocOp; +import org.waveprotocol.wave.model.document.operation.EvaluatingDocOpCursor; +import org.waveprotocol.wave.model.document.operation.impl.AnnotationBoundaryMapImpl; +import org.waveprotocol.wave.model.document.operation.impl.AttributesUpdateImpl; +import org.waveprotocol.wave.model.document.operation.impl.DocOpBuffer; +import org.waveprotocol.wave.model.util.Preconditions; + +/** + * A convenience class for creating document operations. + * + */ +public class DocOpCreator { + + /** + * A builder for BufferedDocOps which is used by the static convenience + * methods of DocOpCreator for creating operations. This builder allows + * calling "retain" with an argument of 0 and "characters" and + * "deleteCharacters" with an empty string argument, in order to make the + * building process easier in some circumstances. + */ + private static class SimplifyingDocOpBuilder { + + private final EvaluatingDocOpCursor<DocOp> buffer = new DocOpBuffer(); + + public final DocOp build() { + return buffer.finish(); + } + + public final SimplifyingDocOpBuilder retain(int itemCount) { + Preconditions.checkArgument(itemCount >= 0, "Negative item count"); + if (itemCount > 0) { + buffer.retain(itemCount); + } + return this; + } + + public final SimplifyingDocOpBuilder characters(String characters) { + if (characters.length() > 0) { + buffer.characters(characters); + } + return this; + } + + public final SimplifyingDocOpBuilder elementStart(String type, Attributes attrs) { + buffer.elementStart(type, attrs); + return this; + } + + public final SimplifyingDocOpBuilder elementEnd() { + buffer.elementEnd(); + return this; + } + + public final SimplifyingDocOpBuilder deleteCharacters(String characters) { + if (characters.length() > 0) { + buffer.deleteCharacters(characters); + } + return this; + } + + public final SimplifyingDocOpBuilder deleteElementStart(String type, Attributes attrs) { + buffer.deleteElementStart(type, attrs); + return this; + } + + public final SimplifyingDocOpBuilder deleteElementEnd() { + buffer.deleteElementEnd(); + return this; + } + + public final SimplifyingDocOpBuilder replaceAttributes(Attributes oldAttrs, + Attributes newAttrs) { + buffer.replaceAttributes(oldAttrs, newAttrs); + return this; + } + + public final SimplifyingDocOpBuilder updateAttributes(AttributesUpdate update) { + buffer.updateAttributes(update); + return this; + } + + public final SimplifyingDocOpBuilder setAnnotation(int itemCount, String key, String oldValue, + String newValue) { + Preconditions.checkArgument(itemCount >= 0, "Negative item count"); + if (itemCount > 0) { + buffer.annotationBoundary(AnnotationBoundaryMapImpl.builder() + .updateValues(key, oldValue, newValue) + .build()); + buffer.retain(itemCount); + buffer.annotationBoundary(AnnotationBoundaryMapImpl.builder() + .initializationEnd(key) + .build()); + } + return this; + } + + } + + /** + * Creates a document operation that inserts the given characters at the given + * location. + * + * @param size The initial size of the document. + * @param location The location at which to insert characters. + * @param characters The characters to insert. + * @return The document operation. + */ + public static DocOp insertCharacters(int size, int location, String characters) { + return new SimplifyingDocOpBuilder() + .retain(location) + .characters(characters) + .retain(size - location) + .build(); + } + + /** + * Creates a document operation that inserts an element at the given location. + * + * @param size The initial size of the document. + * @param location The location at which to insert the element. + * @param type The type of the element. + * @param attributes The attributes of the element. + * @return The document operation. + */ + public static DocOp insertElement(int size, int location, String type, + Attributes attributes) { + return new SimplifyingDocOpBuilder() + .retain(location) + .elementStart(type, attributes) + .elementEnd() + .retain(size - location) + .build(); + } + + /** + * Creates a document operation that deletes the characters denoted by the + * given range. + * + * @param size The initial size of the document. + * @param location The location the characters to delete. + * @param characters The characters to delete. + * @return The document operation. + */ + public static DocOp deleteCharacters(int size, int location, String characters) { + return new SimplifyingDocOpBuilder() + .retain(location) + .deleteCharacters(characters) + .retain(size - location - characters.length()) + .build(); + } + + /** + * Creates a document operation that deletes an empty element at a given + * location. + * + * @param size The initial size of the document. + * @param location The location of the element to delete. + * @param type The type of the element. + * @param attributes The attributes of the element. + * @return The document operation. + */ + public static DocOp deleteElement(int size, int location, String type, + Attributes attributes) { + return new SimplifyingDocOpBuilder() + .retain(location) + .deleteElementStart(type, attributes) + .deleteElementEnd() + .retain(size - location - 2) + .build(); + } + + /** + * Creates a document operation that replace all the attributes of an element. + * + * @param size The initial size of the document. + * @param location The location of the element whose attributes are to be set. + * @param oldAttr The old attributes of the element. + * @param newAttr The new attributes that the element should have. + * @return The document operation. + */ + public static DocOp replaceAttributes(int size, int location, Attributes oldAttr, + Attributes newAttr) { + return new SimplifyingDocOpBuilder() + .retain(location) + .replaceAttributes(oldAttr, newAttr) + .retain(size - location - 1) + .build(); + } + + /** + * Creates a document operation that sets an attribute of an element. + * + * @param size The initial size of the document. + * @param location The location of the element whose attribute is to be set. + * @param name The name of the attribute to set. + * @param oldValue The old value of the attribute. + * @param newValue The value to which to set the attribute. + * @return The document operation. + */ + public static DocOp setAttribute(int size, int location, String name, String oldValue, + String newValue) { + return new SimplifyingDocOpBuilder() + .retain(location) + .updateAttributes(new AttributesUpdateImpl(name, oldValue, newValue)) + .retain(size - location - 1) + .build(); + } + + /** + * Creates a document operation that sets an annotation over a range. + * + * @param size The initial size of the document. + * @param start The location of the start of the range on which the annotation + * is to be set. + * @param end The location of the end of the range on which the annotation is + * to be set. + * @param key The annotation key. + * @param oldValue The old annotation value. + * @param newValue The new annotation value. + * @return The document operation. + */ + public static DocOp setAnnotation(int size, int start, int end, String key, + String oldValue, String newValue) { + return new SimplifyingDocOpBuilder() + .retain(start) + .setAnnotation(end - start, key, oldValue, newValue) + .retain(size - end) + .build(); + } + + /** + * Creates a document operation that acts as the identity on a document. + * + * @param size The size of the document. + * @return The document operation. + */ + public static DocOp identity(int size) { + return new SimplifyingDocOpBuilder() + .retain(size) + .build(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/ExtraAsserts.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/ExtraAsserts.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/ExtraAsserts.java new file mode 100644 index 0000000..989240b --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/ExtraAsserts.java @@ -0,0 +1,92 @@ +/** + * 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.model.testing; + +import org.waveprotocol.wave.model.wave.data.BlipData; + +import junit.framework.Assert; + +import org.waveprotocol.wave.model.document.MutableDocument; +import org.waveprotocol.wave.model.document.ReadableWDocument; +import org.waveprotocol.wave.model.document.operation.impl.DocOpUtil; +import org.waveprotocol.wave.model.document.util.DocCompare; +import org.waveprotocol.wave.model.document.util.XmlStringBuilder; + +/** + * Extra assertions that are useful for tests involving the model. + * + */ +public final class ExtraAsserts { + + /** + * Asserts that the structure of the document and the builder are the same. + */ + public static <N, E extends N, T extends N> void assertStructureEquivalent( + XmlStringBuilder expected, MutableDocument<N, E, T> doc) { + String expectedStr = expected.getXmlString(); + if (!DocCompare.equivalent(DocCompare.STRUCTURE, expectedStr, doc)) { + String docStr = doc.toXmlString(); + String message = "Expected [" + expectedStr + "], found [" + docStr + "]"; + Assert.fail(message); + } + } + + /** + * Asserts that the structure of the two documents are the same. + */ + public static <N1, N2> void assertStructureEquivalent(ReadableWDocument<N1, ?, ?> doc1, + ReadableWDocument<N2, ?, ?> doc2) { + if (!DocCompare.equivalent(DocCompare.STRUCTURE, doc1, doc2)) { + String doc1Str = doc1.toXmlString(); + String doc2Str = doc2.toXmlString(); + String message = "Expected [" + doc1Str + "] found [" + doc2Str + "]"; + Assert.fail(message); + } + } + + /** + * Asserts that the content, both structure and annotations, of the document + * and the builder are the same. + */ + public static <N, E extends N, T extends N> void assertEqual( + XmlStringBuilder expected, MutableDocument<N, E, T> doc) { + String expectedStr = expected.getXmlString(); + if (!DocCompare.equivalent(DocCompare.ALL, expectedStr, doc)) { + String docStr = doc.toXmlString(); + String message = "Expected [" + expectedStr + "], found [" + docStr + "]"; + Assert.fail(message); + } + } + + // Static utility class + private ExtraAsserts() { } + + /** + * Checks the content of a document and asserts it matches the given expected + * content. + * + * @param expectedContent The expected content. + * @param root The content to check. + */ + public static void checkContent(String expectedContent, BlipData root) { + Assert.assertEquals(expectedContent, DocOpUtil.toXmlString(root.getContent().asOperation())); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/Factory.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/Factory.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/Factory.java new file mode 100644 index 0000000..7ae8f7a --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/Factory.java @@ -0,0 +1,34 @@ +/** + * 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.model.testing; + +/** + * Generic factory interface. The intended use within this test package is + * to allow black-box tests, which only test an interface, to be decoupled from + * the construction of the particular instance of that interface to test. + * + * @param <T> type of created instances + */ +public interface Factory<T> { + /** + * Creates an instance. + */ + T create(); +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeDocument.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeDocument.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeDocument.java new file mode 100644 index 0000000..33de8c1 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeDocument.java @@ -0,0 +1,81 @@ +/** + * 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.model.testing; + +import org.waveprotocol.wave.model.document.operation.DocInitialization; +import org.waveprotocol.wave.model.document.operation.DocOp; +import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; +import org.waveprotocol.wave.model.id.WaveletId; +import org.waveprotocol.wave.model.operation.OperationException; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument; + +/** + * A document implementation and factory for use in tests. + * + */ +public class FakeDocument extends ObservablePluggableMutableDocument { + + public static class Factory implements DocumentFactory<FakeDocument> { + + private final SchemaProvider schemas; + + public static Factory create(SchemaProvider schemas) { + return new Factory(schemas); + } + + private Factory(SchemaProvider schemas) { + this.schemas = schemas; + } + + private DocumentSchema getSchemaForId(WaveletId waveletId, String documentId) { + DocumentSchema result = schemas.getSchemaForId(waveletId, documentId); + return (result != null) ? result : DocumentSchema.NO_SCHEMA_CONSTRAINTS; + } + + @Override + public FakeDocument create(final WaveletId waveletId, final String blipId, + DocInitialization content) { + return new FakeDocument(content, getSchemaForId(waveletId, blipId)); + } + } + + private DocOp consumed; + + public FakeDocument(DocInitialization initial, DocumentSchema schema) { + super(schema, initial); + } + + @Override + public void consume(DocOp op) throws OperationException { + super.consume(op); + this.consumed = op; + } + + public DocOp getConsumed() { + return consumed; + } + + @Override + public String toString() { + return toXmlString(); + } +}
