/*****************************************************************************
* ------------------------------------------------------------------------- *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
*****************************************************************************/
package com.google.mu.util;
import static com.google.common.truth.Truth.assertThat;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import com.google.common.testing.ClassSanityTester;
import com.google.common.testing.NullPointerTester;
import com.google.mu.util.Funnel;
@RunWith(JUnit4.class)
public final class FunnelTest {
private final Funnel<String> funnel = new Funnel<>();
@Mock private Batch batch;
@Before public void setUpMocks() {
MockitoAnnotations.initMocks(this);
}
@Test public void emptyFunnel() {
assertThat(funnel.run()).isEmpty();
}
@Test public void testNulls() {
new ClassSanityTester().testNulls(Funnel.class);
new NullPointerTester().testAllPublicInstanceMethods(new Funnel<String>().through(batch::send));
}
@Test public void singleElementFunnel() {
funnel.add("hello");
assertThat(funnel.run()).containsExactly("hello");
}
@Test public void twoElementsFunnel() {
funnel.add("hello");
funnel.add("world");
assertThat(funnel.run()).containsExactly("hello", "world").inOrder();
}
@Test public void batchFunctionNotCalledIfNothingAdded() {
funnel.through(batch::send);
assertThat(funnel.run()).isEmpty();
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithTwoElements() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThat(funnel.run()).containsExactly("one", "two").inOrder();
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithPostConversion() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1, s -> s + s);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThat(funnel.run()).containsExactly("oneone", "two").inOrder();
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithPostConversionThatReturnsNull() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1, s -> null);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThat(funnel.run()).containsExactly(null, "two").inOrder();
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithPostConversionThatThrows() {
MyUncheckedException exception = new MyUncheckedException();
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
Function<String, String> throwingFunction = s -> {throw exception;};
toSpell.accept(1, throwingFunction);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThrows(MyUncheckedException.class, funnel::run);
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithAftereffect() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
AtomicReference<String> spelled = new AtomicReference<>();
toSpell.accept(1, spelled::set);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThat(funnel.run()).containsExactly("one", "two").inOrder();
assertThat(spelled.get()).isEqualTo("one");
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void batchInvokedWithAftereffectThatThrows() {
MyUncheckedException exception = new MyUncheckedException();
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
Consumer<String> throwingEffect = s -> {throw exception;};
toSpell.accept(1, throwingEffect);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one", "two"));
assertThrows(MyUncheckedException.class, funnel::run);
Mockito.verify(batch).send(asList(1, 2));
Mockito.verifyNoMoreInteractions(batch);
}
@Test public void interleavedButRespectsOrder() {
Batch batch2 = Mockito.mock(Batch.class);
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
Funnel.Batch<String, String> toLowerCase = funnel.through(batch2::send);
funnel.add("zero");
toSpell.accept(1);
funnel.add("two");
toLowerCase.accept("THREE");
toSpell.accept(4);
when(batch.send(asList(1, 4))).thenReturn(asList("one", "four"));
when(batch2.send(asList("THREE"))).thenReturn(asList("three"));
assertThat(funnel.run()).containsExactly("zero", "one", "two", "three", "four").inOrder();
Mockito.verify(batch).send(asList(1, 4));
Mockito.verify(batch2).send(asList("THREE"));
Mockito.verifyNoMoreInteractions(batch);
Mockito.verifyNoMoreInteractions(batch2);
}
@Test public void batchReturnsEmpty() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1);
when(batch.send(asList(1))).thenReturn(asList());
assertThrows(IllegalStateException.class, funnel::run);
}
@Test public void batchReturnsLessThanInput() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1);
toSpell.accept(2);
when(batch.send(asList(1, 2))).thenReturn(asList("one"));
assertThrows(IllegalStateException.class, funnel::run);
}
@Test public void batchReturnsMoreThanInput() {
Funnel.Batch<Integer, String> toSpell = funnel.through(batch::send);
toSpell.accept(1);
when(batch.send(asList(1))).thenReturn(asList("one", "two"));
assertThrows(IllegalStateException.class, funnel::run);
}
@SuppressWarnings("serial")
private static class MyUncheckedException extends RuntimeException {
MyUncheckedException() {
super("test");
}
}
private interface Batch {
<F, T> List<T> send(List<F> input);
}
}