// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package fi.jumi.core.ipc.encoding; import fi.jumi.actors.eventizers.Event; import fi.jumi.actors.eventizers.dynamic.DynamicEventizer; import fi.jumi.actors.queue.MessageSender; import fi.jumi.core.ipc.TestUtil; import fi.jumi.core.ipc.buffer.IpcBuffer; import fi.jumi.core.ipc.channel.*; import fi.jumi.core.util.*; import org.junit.Test; import java.lang.reflect.*; import java.util.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; public abstract class EncodingContract<T> { protected final Class<T> interfaceType; private final IpcProtocol.EncodingFactory<T> encodingFactory; public EncodingContract(IpcProtocol.EncodingFactory<T> encodingFactory) { this.interfaceType = getMyTypeArgument(0); this.encodingFactory = encodingFactory; } @SuppressWarnings("unchecked") private Class<T> getMyTypeArgument(int index) { ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass(); assertThat(superclass.getRawType(), is((Type) EncodingContract.class)); return (Class<T>) superclass.getActualTypeArguments()[index]; } @Test public void encodes_and_decodes_all_events() throws Exception { SpyListener<T> spy = new SpyListener<>(interfaceType); exampleUsage(spy.getListener()); spy.replay(); IpcBuffer buffer = TestUtil.newIpcBuffer(); // encode IpcProtocol<T> protocol = newIpcProtocol(buffer); protocol.start(); exampleUsage(sendTo(protocol)); protocol.close(); // decode buffer.position(0); IpcReaders.decodeAll(protocol, spy.getListener()); spy.verify(); } @Test public void example_usage_invokes_every_method_in_the_interface() throws Exception { MethodInvocationSpy<T> spy = new MethodInvocationSpy<>(new DynamicEventizer<>(interfaceType)); exampleUsage(spy.getProxy()); for (Method method : interfaceType.getMethods()) { assertThat("invoked methods", spy.methodInvocations.keySet(), hasItem(method)); } } protected abstract void exampleUsage(T listener) throws Exception; private static final Map<Class<?>, MessageEncoding<?>> encodingsByType = new HashMap<>(); @Test public void the_interface_name_and_version_combination_is_unique() { MessageEncoding<T> actual = encodingFactory.create(null); for (MessageEncoding<?> other : encodingsByType.values()) { if (actual.getInterfaceName().equals(other.getInterfaceName()) && actual.getInterfaceVersion() == other.getInterfaceVersion()) { throw new AssertionError(actual.getClass().getName() + " had the same interface name and version as " + other.getClass().getName() + ": " + actual.getInterfaceName() + " v" + actual.getInterfaceVersion()); } } encodingsByType.put(actual.getClass(), actual); } /** * NOTE: It might be desirable for backward compatibility reasons to loosen this restriction in the future, because * otherwise we will not be able to rename the Java classes without breaking backward compatibility. */ @Test public void the_interface_name_is_in_sync_with_the_actual_Java_interface() throws ClassNotFoundException { MessageEncoding<T> encoding = encodingFactory.create(null); String interfaceName = encoding.getInterfaceName(); assertThat(encoding, is(instanceOf(Class.forName(interfaceName)))); } @Test public void interface_version_starts_from_1() { MessageEncoding<T> encoding = encodingFactory.create(null); int interfaceVersion = encoding.getInterfaceVersion(); assertThat(interfaceVersion, is(greaterThanOrEqualTo(1))); } private IpcProtocol<T> newIpcProtocol(IpcBuffer buffer) { return new IpcProtocol<>(buffer, encodingFactory); } private T sendTo(MessageSender<Event<T>> target) { return new DynamicEventizer<>(interfaceType).newFrontend(target); } }