package com.netopyr.wurmloch.crdt; import io.reactivex.processors.ReplayProcessor; import io.reactivex.subscribers.TestSubscriber; import org.hamcrest.CustomMatcher; import org.reactivestreams.Processor; import org.testng.annotations.Test; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; public class USetTest { // Set functionality @Test public void shouldAddElements() { // given: final USet<UUID> set = new USet<>("ID_1"); // when: final boolean result1 = set.add(UUID.randomUUID()); // then: assertThat(result1, is(true)); // when: final boolean result2 = set.add(UUID.randomUUID()); // then: assertThat(result2, is(true)); } @Test public void shouldReturnSize() { // given: final USet<UUID> set = new USet<>("ID_1"); // then: assertThat(set.size(), is(0)); // when: set.add(UUID.randomUUID()); // then: assertThat(set.size(), is(1)); // when: set.add(UUID.randomUUID()); set.add(UUID.randomUUID()); set.add(UUID.randomUUID()); // then: assertThat(set.size(), is(4)); } @Test public void shouldIterate() { // given: final USet<UUID> set = new USet<>("ID_1"); final UUID uuid1 = UUID.randomUUID(); final UUID uuid2 = UUID.randomUUID(); final UUID uuid3 = UUID.randomUUID(); final UUID uuid4 = UUID.randomUUID(); // then: assertThat(set.iterator().hasNext(), is(false)); // when: set.add(uuid1); final Iterator<UUID> it1 = set.iterator(); // then: assertThat(it1.hasNext(), is(true)); assertThat(it1.next(), is(uuid1)); assertThat(it1.hasNext(), is(false)); // when: set.add(uuid2); set.add(uuid3); set.add(uuid4); final Iterator<UUID> it2 = set.iterator(); // then: final Set<UUID> results = new HashSet<>(); while (it2.hasNext()) { results.add(it2.next()); } assertThat(results, containsInAnyOrder(uuid1, uuid2, uuid3, uuid4)); } @Test public void shouldRemoveElements() { // given: final UUID uuid1 = UUID.randomUUID(); final UUID uuid2 = UUID.randomUUID(); final UUID uuid3 = UUID.randomUUID(); final Set<UUID> expected = new HashSet<>(); expected.addAll(Arrays.asList(uuid1, uuid2, uuid3)); final USet<UUID> set = new USet<>("ID_1"); set.addAll(expected); final Iterator<UUID> it = set.iterator(); // when: final UUID e1 = it.next(); it.remove(); expected.remove(e1); // then: assertThat(set, equalTo(expected)); // when: final UUID e2 = it.next(); it.remove(); expected.remove(e2); // then: assertThat(set, equalTo(expected)); // when: it.next(); it.remove(); // then: assertThat(set, empty()); } // CRDT functionality @SuppressWarnings("unchecked") @Test public void shouldSendNotificationForAdds() { // given: final UUID uuid1 = UUID.randomUUID(); final UUID uuid2 = UUID.randomUUID(); final TestSubscriber<CrdtCommand> subscriber = TestSubscriber.create(); final USet<UUID> set = new USet<>("ID_1"); set.subscribe(subscriber); // when: set.add(uuid1); set.add(uuid2); // then: subscriber.assertNotComplete(); subscriber.assertNoErrors(); assertThat(subscriber.values(), contains( new AddCommandMatcher<>(set.getCrdtId(), uuid1), new AddCommandMatcher<>(set.getCrdtId(), uuid2) )); } @SuppressWarnings("unchecked") @Test public void shouldSendNotificationForRemoves() { // given: final UUID uuid1 = UUID.randomUUID(); final TestSubscriber<CrdtCommand> subscriber = TestSubscriber.create(); final USet<UUID> set = new USet<>("ID_1"); set.subscribe(subscriber); set.add(uuid1); // when: final Iterator<UUID> it = set.iterator(); it.next(); it.remove(); // then: subscriber.assertNotComplete(); subscriber.assertNoErrors(); assertThat(subscriber.values(), contains( new AddCommandMatcher<>(set.getCrdtId(), uuid1), new RemoveCommandMatcher<>(set.getCrdtId(), uuid1) )); } @Test public void shouldHandleAddCommands() { // given: final UUID uuid1 = UUID.randomUUID(); final UUID uuid2 = UUID.randomUUID(); final Processor<USet.USetCommand<UUID>, USet.USetCommand<UUID>> inputStream = ReplayProcessor.create(); final TestSubscriber<CrdtCommand> subscriber = TestSubscriber.create(); final USet<UUID> set = new USet<>("ID_1"); set.subscribeTo(inputStream); set.subscribe(subscriber); final USet.AddCommand<UUID> command1 = new USet.AddCommand<>(set.getCrdtId(), uuid1); final USet.AddCommand<UUID> command2 = new USet.AddCommand<>(set.getCrdtId(), uuid2); // when: inputStream.onNext(command1); inputStream.onNext(command2); // then: assertThat(set, hasSize(2)); assertThat(subscriber.valueCount(), is(2)); subscriber.assertNotComplete(); subscriber.assertNoErrors(); } @Test public void shouldHandleRemoveCommands() { // given: final UUID uuid1 = UUID.randomUUID(); final Processor<USet.USetCommand<UUID>, USet.USetCommand<UUID>> inputStream = ReplayProcessor.create(); final TestSubscriber<CrdtCommand> subscriber = TestSubscriber.create(); final USet<UUID> set = new USet<>("ID_1"); set.subscribeTo(inputStream); set.subscribe(subscriber); final USet.AddCommand<UUID> command1 = new USet.AddCommand<>(set.getCrdtId(), uuid1); final USet.RemoveCommand<UUID> command2 = new USet.RemoveCommand<>(set.getCrdtId(), uuid1); // when: inputStream.onNext(command1); inputStream.onNext(command2); // then: assertThat(set, empty()); assertThat(subscriber.valueCount(), is(2)); subscriber.assertNotComplete(); subscriber.assertNoErrors(); } // Observable functionality private static class RemoveCommandMatcher<T> extends CustomMatcher<CrdtCommand> { private final String crdtId; private final T value; private RemoveCommandMatcher(String crdtId, T value) { super(String.format("RemoveCommandMatcher[crdtId=%s,values=%s]", crdtId, value)); this.crdtId = crdtId; this.value = value; } @SuppressWarnings("unchecked") @Override public boolean matches(Object o) { if (o instanceof USet.RemoveCommand) { final USet.RemoveCommand<T> command = (USet.RemoveCommand<T>) o; return command.getCrdtId().equals(crdtId) && command.getElement().equals(value); } return false; } } private static class AddCommandMatcher<T> extends CustomMatcher<CrdtCommand> { private final String crdtId; private final T value; private AddCommandMatcher(String crdtId, T value) { super(String.format("AddCommandMatcher[crdtId=%s,elementValue=%s]", crdtId, value)); this.crdtId = crdtId; this.value = value; } @Override public boolean matches(Object o) { if (o instanceof USet.AddCommand) { final USet.AddCommand command = (USet.AddCommand) o; return command.getCrdtId().equals(crdtId) && command.getElement().equals(value); } return false; } } }