/* __ __ __ __ __ ___ * \ \ / / \ \ / / __/ * \ \/ / /\ \ \/ / / * \____/__/ \__\____/__/.ɪᴏ * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ */ package io.vavr; import io.vavr.collection.Seq; import io.vavr.control.Try; import io.vavr.collection.Iterator; import io.vavr.collection.List; import io.vavr.collection.Vector; import io.vavr.control.Option; import org.junit.Test; import java.io.Serializable; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Spliterator; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Function; import static java.util.concurrent.CompletableFuture.runAsync; import static io.vavr.collection.Iterator.range; public class LazyTest extends AbstractValueTest { @SuppressWarnings("unchecked") @Override protected <T> Undefined<T> empty() { return (Undefined<T>) Undefined.INSTANCE; } @Override protected <T> Lazy<T> of(T element) { return Lazy.of(() -> element); } @SafeVarargs @Override protected final <T> Lazy<T> of(T... elements) { return of(elements[0]); } @Override protected boolean useIsEqualToInsteadOfIsSameAs() { return false; } @Override protected int getPeekNonNilPerformingAnAction() { return 1; } // -- static narrow @Test public void shouldNarrow() { final String expected = "Zero args"; final Lazy<String> wideFunction = Lazy.of(() -> expected); final Lazy<CharSequence> actual = Lazy.narrow(wideFunction); assertThat(actual.get()).isEqualTo(expected); } // -- of(Supplier) @Test public void shouldNotChangeLazy() { final Lazy<Integer> expected = Lazy.of(() -> 1); assertThat(Lazy.of(expected)).isSameAs(expected); } @Test(expected = NullPointerException.class) public void shouldThrowOnNullSupplier() { Lazy.of(null); } @Test public void shouldMemoizeValues() { final Lazy<Double> testee = Lazy.of(Math::random); final double expected = testee.get(); for (int i = 0; i < 10; i++) { final double actual = testee.get(); assertThat(actual).isEqualTo(expected); } } // -- iterate @Test public void shouldIterate() { final Iterator<Integer> iterator = Lazy.of(() -> 1).iterator(); assertThat(iterator.next()).isEqualTo(1); assertThat(iterator.hasNext()).isFalse(); } // -- peek @Test public void shouldPeek() { final Lazy<Integer> lazy = Lazy.of(() -> 1); final Lazy<Integer> peek = lazy.peek(v -> assertThat(v).isEqualTo(1)); assertThat(peek).isSameAs(lazy); } // -- sequence(Iterable) @Test public void shouldSequenceEmpty() { final List<Lazy<Integer>> testee = List.empty(); final Lazy<Seq<Integer>> sequence = Lazy.sequence(testee); assertThat(sequence.get()).isEqualTo(Vector.empty()); } @Test public void shouldSequenceNonEmptyLazy() { final List<Lazy<Integer>> testee = List.of(1, 2, 3).map(i -> Lazy.of(() -> i)); final Lazy<Seq<Integer>> sequence = Lazy.sequence(testee); assertThat(sequence.get()).isEqualTo(Vector.of(1, 2, 3)); } @Test public void shouldNotEvaluateEmptySequence() { final List<Lazy<Integer>> testee = List.empty(); final Lazy<Seq<Integer>> sequence = Lazy.sequence(testee); assertThat(sequence.isEvaluated()).isFalse(); } @Test public void shouldNotEvaluateNonEmptySequence() { final List<Lazy<Integer>> testee = List.of(1, 2, 3).map(i -> Lazy.of(() -> i)); final Lazy<Seq<Integer>> sequence = Lazy.sequence(testee); assertThat(sequence.isEvaluated()).isFalse(); } @Test public void shouldMapOverLazyValue() { final Lazy<Integer> testee = Lazy.of(() -> 42); final Lazy<Integer> expected = Lazy.of(() -> 21); assertThat(testee.map(i -> i / 2)).isEqualTo(expected); } @Test public void shouldFilterOverLazyValue() { final Lazy<Integer> testee = Lazy.of(() -> 42); final Option<Integer> expectedPositive = Option.some(42); final Option<Integer> expectedNegative = Option.none(); assertThat(testee.filter(i -> i % 2 == 0)).isEqualTo(expectedPositive); assertThat(testee.filter(i -> i % 2 != 0)).isEqualTo(expectedNegative); } @Test public void shouldTransformLazyValue() { final Lazy<Integer> testee = Lazy.of(() -> 42); final Integer expected = 21; final Integer actual = testee.transform(lazy -> lazy.get() / 2); assertThat(actual).isEqualTo(expected); } @Test public void shouldNotBeEmpty() { assertThat(Lazy.of(Option::none).isEmpty()).isFalse(); } @Test public void shouldContainASingleValue() { assertThat(Lazy.of(Option::none).isSingleValued()).isTrue(); } // -- val(Supplier, Class) -- Proxy @Test public void shouldCreateLazyProxy() { final String[] evaluated = new String[] { null }; final CharSequence chars = Lazy.val(() -> { final String value = "Yay!"; evaluated[0] = value; return value; }, CharSequence.class); assertThat(evaluated[0]).isEqualTo(null); assertThat(chars).isEqualTo("Yay!"); assertThat(evaluated[0]).isEqualTo("Yay!"); } @Test(expected = NullPointerException.class) public void shouldThrowWhenCreatingLazyProxyAndSupplierIsNull() { Lazy.val(null, CharSequence.class); } @Test(expected = NullPointerException.class) public void shouldThrowWhenCreatingLazyProxyAndTypeIsNull() { Lazy.val(() -> "", null); } @Test(expected = IllegalArgumentException.class) public void shouldThrowWhenCreatingLazyProxyOfObjectType() { Lazy.val(() -> "", String.class); } @Test public void shouldBehaveLikeValueWhenCreatingProxy() { final CharSequence chars = Lazy.val(() -> "Yay!", CharSequence.class); assertThat(chars.toString()).isEqualTo("Yay!"); } // -- isEvaluated() @Test public void shouldBeAwareOfEvaluated() { final Lazy<Void> lazy = Lazy.of(() -> null); assertThat(lazy.isEvaluated()).isFalse(); assertThat(lazy.isEvaluated()).isFalse(); // remains not evaluated lazy.get(); assertThat(lazy.isEvaluated()).isTrue(); } // -- Serialization @Test public void shouldSerializeDeserializeNonNil() { final Object actual = Serializables.deserialize(Serializables.serialize(Lazy.of(() -> 1))); final Object expected = Lazy.of(() -> 1); assertThat(actual).isEqualTo(expected); } // -- concurrency @Test public void shouldSupportMultithreading() { final AtomicBoolean isEvaluated = new AtomicBoolean(); final AtomicBoolean lock = new AtomicBoolean(); final Lazy<Integer> lazy = Lazy.of(() -> { while (lock.get()) { Try.run(() -> Thread.sleep(300)); } return 1; }); new Thread(() -> { Try.run(() -> Thread.sleep(100)); new Thread(() -> { Try.run(() -> Thread.sleep(100)); lock.set(false); }).start(); isEvaluated.compareAndSet(false, lazy.isEvaluated()); lazy.get(); }).start(); assertThat(isEvaluated.get()).isFalse(); assertThat(lazy.get()).isEqualTo(1); } @Test @SuppressWarnings({ "StatementWithEmptyBody", "rawtypes" }) public void shouldBeConsistentFromMultipleThreads() throws Exception { for (int i = 0; i < 100; i++) { final AtomicBoolean canProceed = new AtomicBoolean(false); final Vector<CompletableFuture<Void>> futures = Vector.range(0, 10).map(j -> { final AtomicBoolean isEvaluated = new AtomicBoolean(false); final Integer expected = ((j % 2) == 1) ? null : j; Lazy<Integer> lazy = Lazy.of(() -> { assertThat(isEvaluated.getAndSet(true)).isFalse(); return expected; }); return Tuple.of(lazy, expected); }).flatMap(t -> range(0, 5).map(j -> runAsync(() -> { while (!canProceed.get()) { /* busy wait */ } assertThat(t._1.get()).isEqualTo(t._2); })) ); final CompletableFuture all = CompletableFuture.allOf(futures.toJavaList().toArray(new CompletableFuture<?>[0])); canProceed.set(true); all.join(); } } // -- equals @SuppressWarnings({ "EqualsBetweenInconvertibleTypes", "EqualsWithItself" }) @Test public void shouldDetectEqualObject() { assertThat(Lazy.of(() -> 1).equals("")).isFalse(); assertThat(Lazy.of(() -> 1).equals(Lazy.of(() -> 1))).isTrue(); assertThat(Lazy.of(() -> 1).equals(Lazy.of(() -> 2))).isFalse(); final Lazy<Integer> same = Lazy.of(() -> 1); assertThat(same.equals(same)).isTrue(); } @Test public void shouldDetectUnequalObject() { assertThat(Lazy.of(() -> 1).equals(Lazy.of(() -> 2))).isFalse(); } // -- hashCode @Test public void shouldComputeHashCode() { assertThat(Lazy.of(() -> 1).hashCode()).isEqualTo(Objects.hash(1)); } // -- toString @Test public void shouldConvertNonEvaluatedValueToString() { final Lazy<Integer> lazy = Lazy.of(() -> 1); assertThat(lazy.toString()).isEqualTo("Lazy(?)"); } @Test public void shouldConvertEvaluatedValueToString() { final Lazy<Integer> lazy = Lazy.of(() -> 1); lazy.get(); assertThat(lazy.toString()).isEqualTo("Lazy(1)"); } // -- spliterator @Test public void shouldHaveSizedSpliterator() { assertThat(Lazy.of(() -> 1).spliterator().hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED)).isTrue(); } @Test public void shouldHaveOrderedSpliterator() { assertThat(Lazy.of(() -> 1).spliterator().hasCharacteristics(Spliterator.ORDERED)).isTrue(); } @Test public void shouldReturnSizeWhenSpliterator() { assertThat(Lazy.of(() -> 1).spliterator().getExactSizeIfKnown()).isEqualTo(1); } // === OVERRIDDEN // -- isLazy @Override @Test public void shouldVerifyLazyProperty() { assertThat(empty().isLazy()).isTrue(); assertThat(of(1).isLazy()).isTrue(); } } /** * Lazy can't be empty. It is a placeholder for an existing value, but only evaluated when needed. * In order to re-use existing unit tests, that are valid for all Value implementations, * we provide here an _imaginary_ empty Lazy implementation, called 'undefined'. * <p> * Note: It is no good idea to leak it outside of the test scope into the core library (otherwise Undefined will be the new null). */ final class Undefined<T> implements Value<T>, Serializable { private static final long serialVersionUID = 1L; static final Undefined<?> INSTANCE = new Undefined<>(); private Lazy<T> prototype = Lazy.of(() -> null); private Undefined() { } @Override public T get() { throw new NoSuchElementException(); } @Override public boolean isAsync() { return prototype.isAsync(); } @Override public boolean isEmpty() { return true; } @Override public boolean isLazy() { return prototype.isLazy(); } @Override public boolean isSingleValued() { return prototype.isSingleValued(); } @Override public Value<T> peek(Consumer<? super T> action) { return this; } @Override public String stringPrefix() { return null; } @Override public Iterator<T> iterator() { return Iterator.empty(); } @SuppressWarnings("unchecked") @Override public <U> Value<U> map(Function<? super T, ? extends U> mapper) { return (Value<U>) this; } @Override public boolean equals(Object o) { return o == INSTANCE; } @Override public int hashCode() { return 1; } @Override public String toString() { return "Lazy()"; } /** * Instance control for object serialization. * * @return The singleton instance of Undefined. * @see java.io.Serializable */ private Object readResolve() { return INSTANCE; } }