/***************************************************************************** * ------------------------------------------------------------------------- * * 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.concurrent; import static com.google.common.truth.Truth.assertThat; import static com.google.mu.util.concurrent.FutureAssertions.assertCauseOf; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.lang.reflect.Method; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.mockito.Mockito; import com.google.common.testing.NullPointerTester; public class UtilsTest { @Test public void testMapList_empty() { assertThat(Utils.mapList(asList(), Object::toString)).isEmpty(); } @Test public void testMapList_nonEmpty() { assertThat(Utils.mapList(asList(1, 2), Object::toString)).containsExactly("1", "2").inOrder(); } @Test public void testNulls() { for (Method method : Utils.class.getDeclaredMethods()) { if (method.isSynthetic()) continue; if (method.getName().equals("cast")) continue; new NullPointerTester().testMethod(null, method); } } @Test public void testTyped_notOfType() { StringCondition condition = Mockito.mock(StringCondition.class); assertThat(Utils.typed(String.class, condition::test).test(1)).isFalse(); verify(condition, never()).test(any(String.class)); } @Test public void testTyped_ofType_false() { StringCondition condition = Mockito.mock(StringCondition.class); when(condition.test("hi")).thenReturn(false); assertThat(Utils.typed(String.class, condition::test).test("hi")).isFalse(); verify(condition).test("hi"); } @Test public void testTyped_ofType_true() { StringCondition condition = Mockito.mock(StringCondition.class); when(condition.test("hi")).thenReturn(true); assertThat(Utils.typed(String.class, condition::test).test("hi")).isTrue(); verify(condition).test("hi"); } @Test public void testCast_notAnInstance() { assertThat(Utils.cast(1, String.class)).isEqualTo(Optional.empty()); } @Test public void testCast_isAnInstance() { assertThat(Utils.cast("hi", String.class)).isEqualTo(Optional.of("hi")); } @Test public void testCast_null() { assertThat(Utils.cast(null, String.class)).isEqualTo(Optional.empty()); assertThrows(NullPointerException.class, () -> Utils.cast("hi", null)); } @Test public void testTyped_doesNotPassCondition() { assertThat(Utils.typed(String.class, x -> true).test(1)).isFalse(); } @Test public void testIfCancelled_pending() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isNull(); } @Test public void testIfCancelled_completed() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); future.complete("good"); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isNull(); } @Test public void testIfCancelled_exception() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); future.completeExceptionally(new RuntimeException()); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isNull(); } @Test public void testIfCancelled_cancellationException() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); CancellationException exception = new CancellationException(); future.completeExceptionally(exception); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isSameAs(exception); } @Test public void testIfCancelled_cancelledWithInterruption() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); future.cancel(true); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isInstanceOf(CancellationException.class); } @Test public void testIfCancelled_cancelledWithoutInterruption() { AtomicReference<CancellationException> cancelled = new AtomicReference<>(); CompletableFuture<String> future = new CompletableFuture<>(); future.cancel(false); Utils.ifCancelled(future, cancelled::set); assertThat(cancelled.get()).isInstanceOf(CancellationException.class); } @Test public void testIfCancelled_callbackExceptionIgnored() { CompletableFuture<String> future = new CompletableFuture<>(); future.cancel(false); Utils.ifCancelled(future, e -> {throw new NullPointerException();}); assertThat(future.isCancelled()).isTrue(); } @Test public void testPropagateCancellation_bothPending() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); assertThat(outer.isCancelled()).isFalse(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isDone()).isFalse(); assertThat(inner.isDone()).isFalse(); } @Test public void testPropagateCancellation_cancellationWithInterruptionPropagated() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); outer.cancel(true); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isTrue(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isTrue(); assertThrows(CancellationException.class, inner::get); } @Test public void testPropagateCancellation_cancellationWithoutInterruptionPropagated() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); outer.cancel(false); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isTrue(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isTrue(); assertThrows(CancellationException.class, inner::get); } @Test public void testPropagateCancellation_completedResultNotPropagated() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); outer.complete("ok"); assertThat(outer.isCancelled()).isFalse(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isFalse(); } @Test public void testPropagateCancellation_exceptionalResultNotPropagated() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); outer.completeExceptionally(new IllegalArgumentException()); assertThat(outer.isCancelled()).isFalse(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isCompletedExceptionally()).isTrue(); assertThat(inner.isCompletedExceptionally()).isFalse(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isFalse(); } @Test public void testPropagateCancellation_innerAlreadyCancelled() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); inner.cancel(false); outer.cancel(false); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isTrue(); assertThat(outer.isCompletedExceptionally()).isTrue(); assertThat(inner.isCompletedExceptionally()).isTrue(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isTrue(); } @Test public void testPropagateCancellation_innerAlreadyCompleted() throws Exception { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); inner.complete("inner"); outer.cancel(false); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isCompletedExceptionally()).isTrue(); assertThat(inner.isCompletedExceptionally()).isFalse(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isTrue(); assertThat(inner.get()).isEqualTo("inner"); } @Test public void testPropagateCancellation_innerAlreadyFailed() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = new CompletableFuture<>(); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); IOException exception = new IOException(); inner.completeExceptionally(exception); outer.cancel(false); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isCompletedExceptionally()).isTrue(); assertThat(inner.isCompletedExceptionally()).isTrue(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isTrue(); assertCauseOf(ExecutionException.class, inner).isSameAs(exception); } @Test public void testPropagateCancellation_innerDoesNotSupportToCompletableFuture() { CompletableFuture<String> outer = new CompletableFuture<>(); CompletableFuture<String> inner = Mockito.spy(new CompletableFuture<>()); assertThat(Utils.propagateCancellation(outer, inner)).isSameAs(outer); Mockito.doThrow(new UnsupportedOperationException()).when(inner).toCompletableFuture(); outer.cancel(false); assertThat(outer.isCancelled()).isTrue(); assertThat(inner.isCancelled()).isFalse(); assertThat(outer.isCompletedExceptionally()).isTrue(); assertThat(inner.isCompletedExceptionally()).isFalse(); assertThat(outer.isDone()).isTrue(); assertThat(inner.isDone()).isFalse(); assertThrows(CancellationException.class, outer::get); } private interface StringCondition { boolean test(String s); } }