http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java new file mode 100644 index 0000000..e9d25b8 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeHashedVersionFactory.java @@ -0,0 +1,45 @@ +/** + * 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.WaveletName; +import org.waveprotocol.wave.model.version.HashedVersion; +import org.waveprotocol.wave.model.version.HashedVersionFactory; + +/** + * A hashed version factory which generates unsigned versions. + * + * @author ano...@google.com (Alex North) + */ +public final class FakeHashedVersionFactory implements HashedVersionFactory { + + public static final HashedVersionFactory INSTANCE = new FakeHashedVersionFactory(); + + @Override + public HashedVersion createVersionZero(WaveletName waveletName) { + return HashedVersion.unsigned(0); + } + + @Override + public HashedVersion create(byte[] appliedDeltaBytes, HashedVersion versionAppliedAt, + int opsApplied) { + return HashedVersion.unsigned(versionAppliedAt.getVersion() + opsApplied); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java new file mode 100644 index 0000000..f46802c --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeIdGenerator.java @@ -0,0 +1,44 @@ +/** + * 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.IdGeneratorImpl; +import org.waveprotocol.wave.model.id.IdGeneratorImpl.Seed; + + +/** + * Id generator suitable for use in testing. + * + */ +public final class FakeIdGenerator { + + // Prevent instantiation + private FakeIdGenerator() {} + + public static IdGenerator create() { + return new IdGeneratorImpl("example.com", new Seed() { + @Override + public String get() { + return "seed"; + } + }); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java new file mode 100644 index 0000000..43bbd34 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeSilentOperationSink.java @@ -0,0 +1,69 @@ +/** + * 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.operation.Operation; +import org.waveprotocol.wave.model.operation.SilentOperationSink; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * A place where you can get a concrete OperationSink.Silent for testing. + * + * @author zdw...@google.com (David Wang) + */ +public class FakeSilentOperationSink<T extends Operation<?>> implements SilentOperationSink<T> { + private LinkedList<T> ops = new LinkedList<T>(); + + /** + * For unit testing + * @return the most recently consumed op + */ + public T getConsumedOp() { + int size = ops.size(); + return (size == 0) ? null : (ops.get(size - 1)); + } + + /** + * {@inheritDoc} + */ + public void consume(T op) { + ops.addLast(op); + } + + /** + * Clears the list of saved operations. + */ + public void clear() { + ops.clear(); + } + + /** + * Gets the list of operations consumed by this sink since it was last + * cleared. + * + * @return the ops, from first consumed through most recently consumed. + */ + public List<T> getOps() { + return Collections.unmodifiableList(ops); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java new file mode 100644 index 0000000..5f74e11 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveView.java @@ -0,0 +1,229 @@ +/** + * 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.operation.SilentOperationSink; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; +import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.wave.ObservableWavelet; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.WaveViewListener; +import org.waveprotocol.wave.model.wave.data.DocumentFactory; +import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl; +import org.waveprotocol.wave.model.wave.opbased.ObservableWaveView; +import org.waveprotocol.wave.model.wave.opbased.OpBasedWavelet; +import org.waveprotocol.wave.model.wave.opbased.WaveViewImpl; + +/** + * Dummy implementation of a wave view. + * + */ +public final class FakeWaveView implements ObservableWaveView, Factory<OpBasedWavelet> { + + public final static class Builder { + private final SchemaProvider schemas; + private IdGenerator idGenerator; + private WaveId waveId; + private ParticipantId viewer; + private SilentOperationSink<? super WaveletOperation> sink; + private WaveViewImpl.WaveletConfigurator configurator; + private DocumentFactory<?> docFactory; + + private Builder(SchemaProvider schemas) { + this.schemas = schemas; + } + + public Builder with(DocumentFactory<?> docFactory) { + this.docFactory = docFactory; + 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 Builder with(SilentOperationSink<? super WaveletOperation> sink) { + this.sink = sink; + return this; + } + + public Builder with(WaveViewImpl.WaveletConfigurator configurator) { + this.configurator = configurator; + return this; + } + + public FakeWaveView build() { + if (idGenerator == null) { + idGenerator = FakeIdGenerator.create(); + } + if (waveId == null) { + waveId = idGenerator.newWaveId(); + } + if (viewer == null) { + viewer = FAKE_PARTICIPANT; + } + if (sink == null) { + sink = SilentOperationSink.VOID; + } + if (configurator == null) { + configurator = WaveViewImpl.WaveletConfigurator.ADD_CREATOR; + } + if (docFactory == null) { + // Document factory that accepts output-sink registrations. + docFactory = FakeDocument.Factory.create(schemas); + } + + // Wavelet factory that does all the work. + OpBasedWaveletFactory waveletFactory = OpBasedWaveletFactory // \u2620 + .builder(schemas) // \u2620 + .with(WaveletDataImpl.Factory.create(docFactory)) // \u2620 + .with(sink) // \u2620 + .with(viewer) // \u2620 + .build(); + + // And the view implementation using that factory. + WaveViewImpl<OpBasedWavelet> view = + WaveViewImpl.create(waveletFactory, waveId, idGenerator, viewer, configurator); + + return new FakeWaveView(waveletFactory, view); + } + } + + private static final ParticipantId FAKE_PARTICIPANT = new ParticipantId("f...@example.com"); + + private final OpBasedWaveletFactory factory; + private final WaveViewImpl<? extends OpBasedWavelet> view; + + /** + * Creates a wave view. + * + * @param factory factory exposing testing hacks + * @param view real view implementation + */ + private FakeWaveView(OpBasedWaveletFactory factory, WaveViewImpl<? extends OpBasedWavelet> view) { + this.factory = factory; + this.view = view; + } + + /** + * @return a builder for a fake wave view. + */ + public static Builder builder(SchemaProvider schemas) { + return new Builder(schemas); + } + + // + // Expose as basic wavelet factory for wavelet-specific tests. + // + + @Override + public OpBasedWavelet create() { + return createWavelet(); + } + + // + // Testing hacks. + // + + public MockParticipationHelper getLastAuthoriser() { + return factory.getLastAuthoriser(); + } + + public WaveletOperationContext.Factory getLastContextFactory() { + return factory.getLastContextFactory(); + } + + public OpBasedWavelet createWavelet(WaveletId id) { + return view.createWavelet(id); + } + + public void removeWavelet(ObservableWavelet wavelet) { + view.removeWavelet(wavelet); + } + + // + // Delegate view implementation to view. + // + + @Override + public OpBasedWavelet createRoot() { + return view.createRoot(); + } + + @Override + public OpBasedWavelet createUserData() { + return view.createUserData(); + } + + @Override + public OpBasedWavelet createWavelet() { + return view.createWavelet(); + } + + @Override + public OpBasedWavelet getRoot() { + return view.getRoot(); + } + + @Override + public OpBasedWavelet getUserData() { + return view.getUserData(); + } + + @Override + public OpBasedWavelet getWavelet(WaveletId waveletId) { + return view.getWavelet(waveletId); + } + + @Override + public Iterable<? extends OpBasedWavelet> getWavelets() { + return view.getWavelets(); + } + + @Override + public WaveId getWaveId() { + return view.getWaveId(); + } + + @Override + public void addListener(WaveViewListener listener) { + view.addListener(listener); + } + + @Override + public void removeListener(WaveViewListener listener) { + view.removeListener(listener); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java new file mode 100644 index 0000000..b281a10 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletDataListener.java @@ -0,0 +1,320 @@ +/** + * 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.version.HashedVersion; +import org.waveprotocol.wave.model.wave.ParticipantId; +import org.waveprotocol.wave.model.wave.data.BlipData; +import org.waveprotocol.wave.model.wave.data.WaveletData; +import org.waveprotocol.wave.model.wave.data.WaveletDataListener; + +/** + * Stub implementation of {@link WaveletDataListener}. Each notification method + * saves the passed parameters for later inspection by accessors. + * + * @author zdw...@google.com (David Wang) + */ +public class FakeWaveletDataListener implements WaveletDataListener { + /** + * The last participantId received from + * {@link #onParticipantAdded(WaveletData, ParticipantId)} + */ + private ParticipantId participantAdded; + + /** + * The last participantId received from + * {@link #onParticipantRemoved(WaveletData, ParticipantId)} + */ + private ParticipantId participantRemoved; + + /** + * The last blip received from {@link #onBlipDataAdded(WaveletData, BlipData)} + */ + private BlipData blipDataAdded; + + /** + * The last oldTitle received from {@link #onTitleChanged(WaveletData, String, String)}. + */ + private String oldTitle; + + /** + * The last newTitle received from {@link #onTitleChanged(WaveletData, String, String)}. + */ + private String newTitle; + + /** + * The last blip target received from any other onBlipXxx method. + */ + private BlipData blipModified; + + /** + * The last old modified time received by + * {@link #onLastModifiedTimeChanged(WaveletData, long, long)} + */ + private long oldLastModifiedTime; + + /** + * The last new modified time received by + * {@link #onLastModifiedTimeChanged(WaveletData, long, long)} + */ + private long newLastModifiedTime; + + /** + * The last contributor received by + * {@link #onBlipDataContributorAdded(WaveletData, BlipData, ParticipantId)} + */ + private ParticipantId blipContributorAdded; + + /** + * The last contributor received by + * {@link #onBlipDataContributorRemoved(WaveletData, BlipData, ParticipantId)} + */ + private ParticipantId blipContributorRemoved; + + /** + * The last old timestamp received by + * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)} + */ + private long blipOldTimestamp; + + /** + * The last new timestamp received by + * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)} + */ + private long blipNewTimestamp; + /** + * The last old version received by + * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)} + */ + private long blipOldVersion; + + /** + * The last new version received by + * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)} + */ + private long blipNewVersion; + + private long oldVersion; + private long newVersion; + + private HashedVersion oldHashedVersion; + private HashedVersion newHashedVersion; + + @Override + public void onParticipantAdded(WaveletData wavelet, ParticipantId participantId) { + this.participantAdded = participantId; + } + + @Override + public void onParticipantRemoved(WaveletData wavelet, ParticipantId participantId) { + this.participantRemoved = participantId; + } + + @Override + public void onLastModifiedTimeChanged(WaveletData waveletData, long oldTime, long newTime) { + this.oldLastModifiedTime = oldTime; + this.newLastModifiedTime = newTime; + } + + @Override + public void onVersionChanged(WaveletData wavelet, long oldVersion, long newVersion) { + this.oldVersion = oldVersion; + this.newVersion = newVersion; + } + + @Override + public void onHashedVersionChanged(WaveletData waveletData, HashedVersion oldHashedVersion, + HashedVersion newHashedVersion) { + this.oldHashedVersion = oldHashedVersion; + this.newHashedVersion = newHashedVersion; + } + + @Override + public void onBlipDataAdded(WaveletData waveletData, BlipData blip) { + this.blipDataAdded = blip; + } + + @Override + public void onBlipDataContributorAdded( + WaveletData waveletData, BlipData blip, ParticipantId contributor) { + this.blipModified = blip; + this.blipContributorAdded = contributor; + } + + @Override + public void onBlipDataContributorRemoved( + WaveletData waveletData, BlipData blip, ParticipantId contributor) { + this.blipModified = blip; + this.blipContributorRemoved = contributor; + } + + @Override + public void onBlipDataTimestampModified( + WaveletData waveletData, BlipData blip, long oldTime, long newTime) { + this.blipModified = blip; + this.blipOldTimestamp = oldTime; + this.blipNewTimestamp = newTime; + } + + @Override + public void onBlipDataVersionModified( + WaveletData waveletData, BlipData blip, long oldVersion, long newVersion) { + this.blipModified = blip; + this.blipOldVersion = oldVersion; + this.blipNewVersion = newVersion; + } + + @Deprecated + @Override + public void onRemoteBlipDataContentModified(WaveletData waveletData, BlipData blip) { + this.blipModified = blip; + } + + @Override + public void onBlipDataSubmitted(WaveletData waveletData, BlipData blip) { + this.blipModified = blip; + } + + /** + * @return the last participantId received by + * {@link #onParticipantAdded(WaveletData, ParticipantId)} + */ + public ParticipantId getParticipantAdded() { + return participantAdded; + } + + /** + * @return the last participantId received by + * {@link #onParticipantRemoved(WaveletData, ParticipantId)} + */ + public ParticipantId getParticipantRemoved() { + return participantRemoved; + } + + /** + * @return the last blip received by {@link #onBlipDataAdded(WaveletData, BlipData)} + */ + public BlipData getBlipDataAdded() { + return blipDataAdded; + } + + /** + * @return the last blip received by any of the other onBlipDataXxx methods. + */ + public BlipData getBlipModified() { + return blipModified; + } + + /** + * @return the last newTitle received by + * {@link #onTitleChanged(WaveletData, String, String)}. + */ + public String getNewTitle() { + return newTitle; + } + + /** + * @return the last oldTitle received by + * {@link #onTitleChanged(WaveletData, String, String)}. + */ + public String getOldTitle() { + return oldTitle; + } + + /** + * @return the last old time received by + * {@link #onLastModifiedTimeChanged(WaveletData, long, long)} + */ + public long getOldLastModifiedTime() { + return oldLastModifiedTime; + } + + /** + * @return the last new time received by + * {@link #onLastModifiedTimeChanged(WaveletData, long, long)} + */ + public long getNewLastModifiedTime() { + return newLastModifiedTime; + } + + /** + * @return the last participant received by + * {@link #onBlipDataContributorAdded(WaveletData, BlipData, ParticipantId)} + */ + public ParticipantId getBlipContributorAdded() { + return blipContributorAdded; + } + + /** + * @return the last participant receieved by + * {@link #onBlipDataContributorRemoved(WaveletData, BlipData, ParticipantId)} + */ + public ParticipantId getBlipContributorRemoved() { + return blipContributorRemoved; + } + + /** + * @return the last old timestamp received by + * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)} + */ + public long getBlipOldTimestamp() { + return blipOldTimestamp; + } + + /** + * @return the last new timestamp received by + * {@link #onBlipDataTimestampModified(WaveletData, BlipData, long, long)} + */ + public long getBlipNewTimestamp() { + return blipNewTimestamp; + } + + /** + * @return the last new version received by + * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)} + */ + public long getBlipOldVersion() { + return blipOldVersion; + } + + /** + * @return the last old version received by + * {@link #onBlipDataVersionModified(WaveletData, BlipData, long, long)} + */ + public long getBlipNewVersion() { + return blipNewVersion; + } + + public long getNewVersion() { + return newVersion; + } + + public long getOldVersion() { + return oldVersion; + } + + public HashedVersion getNewHashedVersion() { + return newHashedVersion; + } + + public HashedVersion getOldHashedVersion() { + return oldHashedVersion; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java new file mode 100644 index 0000000..8d66614 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/FakeWaveletListener.java @@ -0,0 +1,58 @@ +/** + * 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.ObservableWavelet; +import org.waveprotocol.wave.model.wave.WaveletListener; +import org.waveprotocol.wave.model.wave.opbased.WaveletListenerImpl; + +import org.waveprotocol.wave.model.wave.ParticipantId; + +/** + * Stub implementation of {@link WaveletListener}. Each notification method + * saves the passed parameters for later inspection by accessors. + * + * @author zdw...@google.com (David Wang) + */ +public class FakeWaveletListener extends WaveletListenerImpl { + /** The last participant received from + * {@link #onParticipantAdded(ObservableWavelet, ParticipantId)} + */ + private ParticipantId participant; + + @Override + public void onParticipantAdded(ObservableWavelet wavelet, ParticipantId participant) { + this.participant = participant; + } + + /** + * @return the last {@code participant} received by + * {@link #onParticipantAdded(ObservableWavelet, ParticipantId)}. + */ + public ParticipantId getParticipant() { + return participant; + } + + + /** Resets all fields. */ + public void reset() { + this.participant = null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java new file mode 100644 index 0000000..98ce42b --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericGWTTestBase.java @@ -0,0 +1,77 @@ +/** + * 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 com.google.gwt.junit.client.GWTTestCase; + +/** + * The base class for running a model-related test case as a GWT test case. + * + * A {@link GenericGWTTestBase} contains a {@link GenericTestBase}, to which it forwards all + * relevant testing methods. This base class holds the reference to the + * contained test, and forwards {@link GWTTestCase#gwtSetUp()} and + * {@link GWTTestCase#gwtTearDown()} to it. + * + * To run a vanilla JUnit test case as a GWTTestCase, simply write the JUnit + * test as an extension of {@link GenericTestBase}, and create a parallel extension + * of this class that wraps an instance of the plain test case, and forwards + * all test methods to it. + * + * @param <T> wrapped test case class + */ +public abstract class GenericGWTTestBase<T extends GenericTestBase<?>> extends GWTTestCase { + /** The wrapped vanilla test case. */ + protected final T target; + + /** + * The default constructor. + */ + protected GenericGWTTestBase(T target) { + this.target = target; + } + + /** + * Forwards to wrapped test's {@link GenericTestBase#setUp()}. + */ + @Override + protected void gwtSetUp() throws Exception { + target.setUp(); + } + + /** + * Forwards to wrapped test's {@link GenericTestBase#tearDown()}. + */ + @Override + protected void gwtTearDown() throws Exception { + target.tearDown(); + } + + /** + * Specifies a module to use when running this test case. The returned + * module must cause the source for this class to be included. + * + * @see com.google.gwt.junit.client.GWTTestCase#getModuleName() + */ + @Override + public String getModuleName() { + return "org.waveprotocol.wave.model.tests"; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java new file mode 100644 index 0000000..980a987 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/GenericTestBase.java @@ -0,0 +1,70 @@ +/** + * 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 junit.framework.TestCase; + +/** + * Generic base implementation for a test case that tests the behaviour of a + * single type. This implementation holds a reference to a factory for + * creating instances of that interface, and uses that factory to instantiates + * the instance to test in {@link #setUp()}. + * + * @param <T> interface type being tested + */ +public abstract class GenericTestBase<T> extends TestCase { + /** Factory used to create each wave to be tested. */ + protected final Factory<? extends T> factory; + + // State initialized in setUp() + + /** Target to test. */ + protected T target; + + /** + * Creates this test case, which runs on the wave-datas created by a factory. + * + * @param factory factory for creating the wave-datas to test + */ + protected GenericTestBase(Factory<? extends T> factory) { + this.factory = factory; + } + + /** + * {@inheritDoc} + * + * This implementation uses the test's factory to creates a test target. + */ + @Override + protected void setUp() { + target = factory.create(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void tearDown() throws Exception { + // This is only overridden to expose tearDown to GWTTestBase (which should + // be in GWTTestBase's scope anyway, since it extends TestCase, but for + // some reason it isn't). + super.tearDown(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java new file mode 100644 index 0000000..bca7824 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockParticipationHelper.java @@ -0,0 +1,111 @@ +/** + * 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.ParticipantId; +import org.waveprotocol.wave.model.wave.ParticipationHelper; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Mock {@link ParticipationHelper}. + * + */ +public class MockParticipationHelper implements ParticipationHelper { + + /** + * Frame used with {@link MockParticipationHelper} to record expectations + * and desired results. + */ + public static class Frame { + private final Set<ParticipantId> candidates; + private final ParticipantId editor; + private final ParticipantId result; + + /** + * Creates a frame that will either return a given participant or throw an + * {@link IllegalStateException} if no participant is given. + * + * @param result participant to return from this frame, or null if an + * {@link IllegalStateException} should be thrown. + * @param editor required for this frame to apply. + * @param candidates required for this frame to apply. + */ + public Frame(ParticipantId result, ParticipantId editor, + ParticipantId... candidates) { + this.result = result; + this.editor = editor; + this.candidates = new HashSet<ParticipantId>(Arrays.asList(candidates)); + } + + /** Returns the result or throws the exception dictated by this frame. */ + public ParticipantId apply() { + if (result == null) { + throw new IllegalStateException("Authoriser set to throw exception on this frame."); + } else { + return result; + } + } + + /** Checks whether the given arguments match those expected by this frame. */ + public boolean matches(ParticipantId editor, Set<ParticipantId> candidates) { + return editor.equals(this.editor) && candidates.equals(this.candidates); + } + } + + private final LinkedList<Frame> frames = new LinkedList<Frame>(); + + /** + * {@inheritDoc} + * + * Makes a decision by comparing against the next frame in the stub. If + * successful, that frame will then be discarded. + * + * @return the return participant of the frame if the arguments match those of + * the frame and the frame includes a return participant. + * @throws IllegalStateException if the arguments match those of the frame and + * the frame is designed to throw such an exception. + * @throws AssertionError if the arguments do not match those of the frame. + * @throws NoSuchElementException if there are no frames left. + */ + @Override + public ParticipantId getAuthoriser(ParticipantId editor, Set<ParticipantId> candidates) { + if (frames.isEmpty()) { + throw new NoSuchElementException("No frames left to compare with getAuthoriser(" + + editor + ", " + candidates + ")"); + } else { + Frame frame = frames.removeFirst(); + if (frame.matches(editor, candidates)) { + return frame.apply(); + } else { + throw new AssertionError(); + } + } + } + + /** Adds a given frame to the end of the list of those expected by this stub. */ + public void program(Frame frame) { + frames.addLast(frame); + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java new file mode 100644 index 0000000..df78ca4 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/MockWaveletOperationContextFactory.java @@ -0,0 +1,61 @@ +/** + * 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.operation.wave.AbstractWaveletOperationContextFactory; +import org.waveprotocol.wave.model.wave.ParticipantId; + +/** + * WaveletOperationContext.Factory that supports setting the timestamp + * and default participant id to use. + * + */ +public class MockWaveletOperationContextFactory extends AbstractWaveletOperationContextFactory { + private long timeMillis; + private ParticipantId participantId; + + @Override + protected long currentTimeMillis() { + return timeMillis; + } + + @Override + public ParticipantId getParticipantId() { + return participantId; + } + + /** + * Sets the timestamp for future WaveletOperationContext objects generated + * by this factory. + */ + public MockWaveletOperationContextFactory setCurrentTimeMillis(long timeMillis) { + this.timeMillis = timeMillis; + return this; + } + + /** + * Sets the participant for future WaveletOperationContext objects generated + * by this factory. + */ + public MockWaveletOperationContextFactory setParticipantId(ParticipantId participantId) { + this.participantId = participantId; + return this; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java new file mode 100644 index 0000000..8df4800 --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/ModelTestUtils.java @@ -0,0 +1,61 @@ +/** + * 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.impl.AttributesImpl; +import org.waveprotocol.wave.model.document.operation.impl.DocInitializationBuilder; + +/** + * A utility class containing convenient methods for creating and checking blip + * document content. + * + */ +public final class ModelTestUtils { + + private ModelTestUtils() { + } + + /** + * Creates a document with the given content. + * + * @param contentText The content that the document should have. + * @return The document with the given content. + */ + public static DocInitialization createContent(String contentText) { + if (contentText.isEmpty()) { + return (new DocInitializationBuilder()) + .elementStart("body", new AttributesImpl()) + .elementStart("line", new AttributesImpl()) + .elementEnd() + .elementEnd() + .build(); + } else { + return new DocInitializationBuilder() + .elementStart("body", new AttributesImpl()) + .elementStart("line", new AttributesImpl()) + .elementEnd() + .characters(contentText) + .elementEnd() + .build(); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java new file mode 100644 index 0000000..a2011bf --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpBasedWaveletFactory.java @@ -0,0 +1,194 @@ +/** + * 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.operation.OperationException; +import org.waveprotocol.wave.model.operation.OperationRuntimeException; +import org.waveprotocol.wave.model.operation.SilentOperationSink; +import org.waveprotocol.wave.model.operation.SilentOperationSink.Executor; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; +import org.waveprotocol.wave.model.schema.SchemaProvider; +import org.waveprotocol.wave.model.version.HashedVersion; +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.WaveletData; +import org.waveprotocol.wave.model.wave.data.impl.EmptyWaveletSnapshot; +import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument; +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; + +/** + * Factory for creating {@link OpBasedWavelet} instances suitable for testing. + * + */ +public final class OpBasedWaveletFactory implements WaveViewImpl.WaveletFactory<OpBasedWavelet>, + Factory<OpBasedWavelet> { + + /** + * An operation sink that, on every operation it consumes, fires a version + * update operation back to the wavelet, then passes the operation along to + * the next sink. Wavelet versioning is specifically designed to be + * server-controlled. In a test context, this sink is used to simulate the + * behaviour of a wavelet server firing back acknowledgements with version + * updates, in order that tests that mutate wavelets also see version number + * increase. + */ + private final static class VersionIncrementingSink implements + SilentOperationSink<WaveletOperation> { + private final WaveletData data; + private final SilentOperationSink<? super WaveletOperation> output; + + public VersionIncrementingSink(WaveletData data, + SilentOperationSink<? super WaveletOperation> output) { + this.data = data; + this.output = output; + } + + @Override + public void consume(WaveletOperation op) { + // Update local version, simulating server response. + try { + op.createVersionUpdateOp(1, null).apply(data); + } catch (OperationException e) { + throw new OperationRuntimeException("test sink verison update failed", e); + } + + // Pass to output sink. + output.consume(op); + } + } + + /** + * Builder, through which a factory can be conveniently configured. + */ + public final static class Builder { + private final SchemaProvider schemas; + private ObservableWaveletData.Factory<?> holderFactory; + private SilentOperationSink<? super WaveletOperation> sink; + private ParticipantId author; + + public Builder(SchemaProvider schemas) { + this.schemas = schemas; + } + + public Builder with(SilentOperationSink<? super WaveletOperation> sink) { + this.sink = sink; + return this; + } + + public Builder with(ObservableWaveletData.Factory<?> holderFactory) { + this.holderFactory = holderFactory; + return this; + } + + public Builder with(ParticipantId author) { + this.author = author; + return this; + } + + public OpBasedWaveletFactory build() { + if (holderFactory == null) { + DocumentFactory<?> docFactory = ObservablePluggableMutableDocument.createFactory(schemas); + holderFactory = WaveletDataImpl.Factory.create(docFactory); + } + if (sink == null) { + sink = SilentOperationSink.VOID; + } + if (author == null) { + // Old tests expect this. + author = FAKE_PARTICIPANT; + } + return new OpBasedWaveletFactory(holderFactory, sink, author); + } + } + + private static final ParticipantId FAKE_PARTICIPANT = new ParticipantId("f...@example.com"); + + // Parameters with which to create the OpBasedWavelets. + private final ObservableWaveletData.Factory<?> holderFactory; + private final SilentOperationSink<? super WaveletOperation> sink; + private final ParticipantId author; + + // Testing hacks. + private MockWaveletOperationContextFactory lastContextFactory; + private MockParticipationHelper lastAuthoriser; + + /** + * Creates a factory, which creates op-based waves that adapt wave data + * holders provided by another factory, sending produced operations to a given + * sink. + * + * @param holderFactory factory for providing wave data holders + * @param sink sink to which produced operations are sent + * @param author id to which edits are to be attributed + */ + private OpBasedWaveletFactory(ObservableWaveletData.Factory<?> holderFactory, + SilentOperationSink<? super WaveletOperation> sink, + ParticipantId author) { + this.holderFactory = holderFactory; + this.sink = sink; + this.author = author; + } + + public static Builder builder(SchemaProvider schemas) { + return new Builder(schemas); + } + + @Override + public OpBasedWavelet create() { + IdGenerator gen = FakeIdGenerator.create(); + return create(gen.newWaveId(), gen.newConversationWaveletId(), FAKE_PARTICIPANT); + } + + @Override + public OpBasedWavelet create(WaveId waveId, WaveletId waveletId, ParticipantId creator) { + long now = System.currentTimeMillis(); + HashedVersion v0 = HashedVersion.unsigned(0); + ObservableWaveletData waveData = holderFactory + .create(new EmptyWaveletSnapshot(waveId, waveletId, creator, v0, now)); + lastContextFactory = new MockWaveletOperationContextFactory().setParticipantId(author); + lastAuthoriser = new MockParticipationHelper(); + SilentOperationSink<WaveletOperation> executor = + Executor.<WaveletOperation, WaveletData>build(waveData); + SilentOperationSink<WaveletOperation> out = new VersionIncrementingSink(waveData, sink); + return new OpBasedWavelet(waveId, waveData, lastContextFactory, lastAuthoriser, executor, out); + } + + /** + * Gets the authoriser provided to help the last {@link OpBasedWavelet} that + * was created. The result is undefined if no wavelets have been created. + */ + public MockParticipationHelper getLastAuthoriser() { + return lastAuthoriser; + } + + /** + * Gets the helper provided to the last {@link OpBasedWavelet} that was + * created. The result is undefined if no wavelets have been created. + */ + public MockWaveletOperationContextFactory getLastContextFactory() { + return lastContextFactory; + } +} http://git-wip-us.apache.org/repos/asf/incubator-wave/blob/314ddcc7/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java ---------------------------------------------------------------------- diff --git a/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.java new file mode 100644 index 0000000..8c17f5c --- /dev/null +++ b/wave/src/test/java/org/waveprotocol/wave/model/testing/OpMatchers.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.testing; + +import org.waveprotocol.wave.model.operation.wave.AddParticipant; +import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; +import org.waveprotocol.wave.model.operation.wave.WaveletOperation; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Hamcrest matchers for CWM operations. Many of these are for use in JMock + * tests as replacements for the deprecated and broken + * {@link org.jmock.Expectations#a(Class)} and non-typesafe alternative + * {@link org.hamcrest.Matchers#instanceOf(Class)}. + * + */ +public class OpMatchers { + /** + * Alternative to Matchers.a(AddParticipant.class) since JMock/Hamcrest's + * implementation is deprecated due to being broken in Java 5 and 6. + */ + public static Matcher<WaveletOperation> addParticipantOperation() { + return new BaseMatcher<WaveletOperation>() { + @Override + public boolean matches(Object obj) { + return obj instanceof AddParticipant; + } + + @Override + public void describeTo(Description description) { + description.appendText(" instanceof AddParticipant"); + } + }; + } + + /** Creates a matcher for operations created by the given author. */ + public static Matcher<WaveletOperation> opBy(final String author) { + return new TypeSafeMatcher<WaveletOperation>() { + @Override + public boolean matchesSafely(WaveletOperation op) { + return author.equals(op.getContext().getCreator().getAddress()); + } + + @Override + public void describeTo(Description description) { + description.appendText(" op created by " + author); + } + }; + } + + /** + * Alternative to Matchers.a(WaveletBlipOperation.class) since + * JMock/Hamcrest's implementation is deprecated due to being broken in Java 5 + * and 6. + */ + public static Matcher<WaveletOperation> waveletBlipOperation() { + return new BaseMatcher<WaveletOperation>() { + @Override + public boolean matches(Object obj) { + return obj instanceof WaveletBlipOperation; + } + + @Override + public void describeTo(Description description) { + description.appendText(" instanceof WaveletBlipOperation"); + } + }; + } + + /** Uninstantiable. */ + private OpMatchers() { + } +}