diff --git a/pom.xml b/pom.xml index a297970..6c9fcf9 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ org.junit junit-bom - 5.10.2 + 5.11.1 pom import @@ -63,7 +63,7 @@ org.slf4j slf4j-bom - 2.0.12 + 2.0.16 pom import @@ -79,13 +79,13 @@ org.hamcrest hamcrest - 2.2 + 3.0 test nl.jqno.equalsverifier equalsverifier - 3.15.6 + 3.17 test @@ -93,10 +93,15 @@ mockito-core test + + org.mockito + mockito-junit-jupiter + test + commons-io commons-io - 2.15.1 + 2.17.0 test @@ -158,11 +163,11 @@ com.mycila license-maven-plugin - 4.3 + 4.6 maven-enforcer-plugin - 3.4.1 + 3.5.0 @@ -185,7 +190,7 @@ maven-compiler-plugin - 3.12.1 + 3.13.0 -Xlint @@ -194,23 +199,23 @@ maven-surefire-plugin - 3.2.5 + 3.5.0 maven-deploy-plugin - 3.1.1 + 3.1.3 maven-clean-plugin - 3.3.2 + 3.4.0 maven-dependency-plugin - 3.6.1 + 3.8.0 maven-install-plugin - 3.1.1 + 3.1.3 maven-resources-plugin @@ -218,7 +223,7 @@ maven-javadoc-plugin - 3.6.3 + 3.10.0 @@ -231,12 +236,12 @@ maven-jar-plugin - 3.3.0 + 3.4.2 org.codehaus.mojo versions-maven-plugin - 2.16.2 + 2.17.1 false @@ -244,7 +249,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.18.5 + 0.23.0 diff --git a/src/main/java/no/digipost/DiggBase.java b/src/main/java/no/digipost/DiggBase.java index fd48e69..63451f3 100644 --- a/src/main/java/no/digipost/DiggBase.java +++ b/src/main/java/no/digipost/DiggBase.java @@ -23,10 +23,12 @@ import java.util.Deque; import java.util.Objects; import java.util.Optional; +import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static java.util.Spliterator.ORDERED; import static java.util.Spliterators.spliterator; @@ -220,8 +222,8 @@ public static Stream close(AutoCloseable ... closeables) { /** * Create a stream which will yield the exceptions (if any) from invoking an {@link ThrowingConsumer action} on - * several {@code instances}. Consuming the stream will ensure that all instances will have - * the action invoked on them, and any exceptions happening will be available through the returned stream. + * several {@code instances}. Consuming the returned stream will ensure that all instances will have + * the action attempted on them, and any exceptions happening will be available through the returned stream. * * @param action the action to execute for each provided instance * @param instances the instances to act on with the provided {@code action}. @@ -237,8 +239,12 @@ public static Stream forceOnAll(ThrowingConsumerall instances will have - * the action invoked on them, and any exceptions happening will be available through the returned stream. + * several {@code instances}. This also includes exceptions thrown from traversing the given {@link Stream} + * of instances, i.e. should resolving an element from the {@code Stream} cause an exception, it will be caught and + * included in the returned {@code Stream}. + *

+ * Consuming the returned stream will ensure that all traversed instances will have + * the action attempted on them, and any exceptions happening will be available through the returned stream. * * @param action the action to execute for each provided instance * @param instances the instances to act on with the provided {@code action}. @@ -246,15 +252,49 @@ public static Stream forceOnAll(ThrowingConsumer Stream forceOnAll(ThrowingConsumer action, Stream instances) { - return instances.filter(Objects::nonNull).flatMap(instance -> { + return StreamSupport.stream( + new FlatMapToExceptionSpliterator<>(action, instances.filter(Objects::nonNull).spliterator()), + instances.isParallel()); + } + + private static final class FlatMapToExceptionSpliterator implements Spliterator { + + private final ThrowingConsumer action; + private final Spliterator wrappedSpliterator; + private final int characteristics; + + FlatMapToExceptionSpliterator(ThrowingConsumer action, Spliterator wrappedSpliterator) { + this.action = action; + this.wrappedSpliterator = wrappedSpliterator; + this.characteristics = wrappedSpliterator.characteristics() & ~(SIZED | SUBSIZED | SORTED); + } + + @Override + public boolean tryAdvance(Consumer exceptionConsumer) { try { - action.accept(instance); - } catch (Exception exception) { - return Stream.of(exception); + return wrappedSpliterator.tryAdvance(action.ifException(exceptionConsumer::accept)); + } catch (Exception e) { + exceptionConsumer.accept(e); + return true; } - return Stream.empty(); - }); - } + } + + @Override + public Spliterator trySplit() { + Spliterator triedSplit = wrappedSpliterator.trySplit(); + return triedSplit != null ? new FlatMapToExceptionSpliterator<>(action, triedSplit) : null; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return characteristics; + } + }; /** diff --git a/src/main/java/no/digipost/DiggStreams.java b/src/main/java/no/digipost/DiggStreams.java index 5cac7b9..7ac88d9 100644 --- a/src/main/java/no/digipost/DiggStreams.java +++ b/src/main/java/no/digipost/DiggStreams.java @@ -19,6 +19,7 @@ import no.digipost.function.ObjLongFunction; import java.util.Collection; +import java.util.Objects; import java.util.Spliterator; import java.util.Spliterators; import java.util.concurrent.atomic.AtomicInteger; @@ -31,6 +32,18 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static java.lang.Integer.toBinaryString; +import static java.util.Spliterator.CONCURRENT; +import static java.util.Spliterator.DISTINCT; +import static java.util.Spliterator.IMMUTABLE; +import static java.util.Spliterator.NONNULL; +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterator.SIZED; +import static java.util.Spliterator.SORTED; +import static java.util.Spliterator.SUBSIZED; +import static java.util.stream.Collectors.joining; +import static no.digipost.DiggBase.friendlyName; + /** * Utilities for working with {@link Stream}s. */ @@ -197,6 +210,35 @@ public boolean tryAdvance(Consumer action) { } + /** + * Get a description of {@link Spliterator#characteristics() characteristics} of a + * {@code Spliterator}. The returned text is is solely intended for debugging and + * logging purposes, and contents and format may change at any time. + * + * @param spliterator the Spliterator + * @return the description + */ + public static String describeCharacteristics(Spliterator spliterator) { + int value = spliterator.characteristics(); + if (value == 0) { + return friendlyName(spliterator.getClass()) + " with no enabled characteristics"; + } else { + String enabledCharacteristics = Stream.of( + spliterator.hasCharacteristics(SIZED) ? "sized" : null, + spliterator.hasCharacteristics(SUBSIZED) ? "subsized" : null, + spliterator.hasCharacteristics(DISTINCT) ? "distinct" : null, + spliterator.hasCharacteristics(NONNULL) ? "non-null" : null, + spliterator.hasCharacteristics(IMMUTABLE) ? "immutable" : null, + spliterator.hasCharacteristics(ORDERED) ? "ordered" : null, + spliterator.hasCharacteristics(CONCURRENT) ? "concurrent" : null, + spliterator.hasCharacteristics(SORTED) ? "sorted" : null) + .filter(Objects::nonNull) + .collect(joining(", ", "", "")); + return enabledCharacteristics + " " + friendlyName(spliterator.getClass()) + " (" + toBinaryString(value) + ")"; + } + } + + private DiggStreams() { } diff --git a/src/test/java/no/digipost/DiggBaseTest.java b/src/test/java/no/digipost/DiggBaseTest.java index 10617e2..f35cfd4 100644 --- a/src/test/java/no/digipost/DiggBaseTest.java +++ b/src/test/java/no/digipost/DiggBaseTest.java @@ -17,44 +17,60 @@ import no.digipost.util.AutoClosed; import no.digipost.util.ThrowingAutoClosed; -import org.hamcrest.Matchers; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.quicktheories.WithQuickTheories; import org.quicktheories.core.Gen; import org.quicktheories.dsl.TheoryBuilder; +import uk.co.probablyfine.matchers.StreamMatchers; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.iterate; +import static java.util.stream.IntStream.rangeClosed; import static java.util.stream.Stream.generate; import static no.digipost.DiggBase.autoClose; import static no.digipost.DiggBase.close; +import static no.digipost.DiggBase.forceOnAll; import static no.digipost.DiggBase.friendlyName; import static no.digipost.DiggBase.nonNull; import static no.digipost.DiggBase.throwingAutoClose; +import static no.digipost.DiggCollectors.toSingleExceptionWithSuppressed; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isA; import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static uk.co.probablyfine.matchers.Java8Matchers.where; -import static uk.co.probablyfine.matchers.StreamMatchers.contains; -import static uk.co.probablyfine.matchers.StreamMatchers.empty; +@ExtendWith(MockitoExtension.class) public class DiggBaseTest implements WithQuickTheories { @Test @@ -89,13 +105,20 @@ public void throwsExceptionWithDescriptionInMessage() { @Test public void extractOptionalValuesFromAnObject() { - assertThat(DiggBase.extractIfPresent("abc", s -> Optional.of(s.charAt(0)), s -> Optional.empty(), s -> Optional.of(s.charAt(2))), contains('a', 'c')); - assertThat(DiggBase.extractIfPresent("abc", s -> Optional.empty(), s -> Optional.empty()), empty()); + assertThat(DiggBase.extractIfPresent("abc", + s -> Optional.of(s.charAt(0)), + s -> Optional.empty(), + s -> Optional.of(s.charAt(2))), + StreamMatchers.contains('a', 'c')); + assertThat(DiggBase.extractIfPresent("abc", + s -> Optional.empty(), + s -> Optional.empty()), + StreamMatchers.empty()); } @Test public void extractValuesIncludesEverythingEvenNulls() { - assertThat(DiggBase.extract("abc", s -> s.charAt(0), s -> null), contains('a', null)); + assertThat(DiggBase.extract("abc", s -> s.charAt(0), s -> null), StreamMatchers.contains('a', null)); } @Test @@ -124,12 +147,12 @@ public void objectManagedByAutoCloseIsSameInstanceAsGiven() { } + interface MyResource { + void done(); + } + @Test - public void useArbitraryObjectWithTryWithResources() { - abstract class MyResource { - abstract void done(); - } - MyResource resource = mock(MyResource.class); + public void useArbitraryObjectWithTryWithResources(@Mock MyResource resource) { try (ThrowingAutoClosed managedResource = throwingAutoClose(resource, MyResource::done)) { verifyNoInteractions(resource); managedResource.object(); //just to avoid javac lint warning @@ -138,14 +161,16 @@ abstract class MyResource { verifyNoMoreInteractions(resource); } + + interface MyAutoCloseableResource extends AutoCloseable { + void done() throws IOException; + @Override + void close() throws RuntimeException; + } + @Test - public void wrappingAnAlreadyAutoCloseableWithAutoCloseWillAlsoInvokeClose() throws Exception { - abstract class MyResource implements AutoCloseable { - abstract void done() throws IOException; - @Override public abstract void close() throws IOException; - } - MyResource resource = mock(MyResource.class); - try (ThrowingAutoClosed managedResource = throwingAutoClose(resource, MyResource::done)) { + public void wrappingAnAlreadyAutoCloseableWithAutoCloseWillAlsoInvokeClose(@Mock MyAutoCloseableResource resource) throws Exception { + try (ThrowingAutoClosed managedResource = throwingAutoClose(resource, MyAutoCloseableResource::done)) { verifyNoInteractions(resource); managedResource.object(); //just to avoid javac lint warning } @@ -156,11 +181,7 @@ abstract class MyResource implements AutoCloseable { } @Test - public void autoCloseWithoutCheckedException() { - abstract class MyResource { - abstract void done(); - } - MyResource resource = mock(MyResource.class); + public void autoCloseWithoutCheckedException(@Mock MyResource resource) { try (AutoClosed managedResource = autoClose(resource, MyResource::done)) { verifyNoInteractions(resource); managedResource.object(); //just to avoid javac lint warning @@ -170,8 +191,7 @@ abstract class MyResource { } @Test - public void getAllExceptionsFromClosingSeveralAutoCloseables() throws Exception { - AutoCloseable closeable = mock(AutoCloseable.class); + public void getAllExceptionsFromClosingSeveralAutoCloseables(@Mock AutoCloseable closeable) throws Exception { doNothing() .doThrow(new IOException()) .doNothing() @@ -183,11 +203,94 @@ public void getAllExceptionsFromClosingSeveralAutoCloseables() throws Exception Stream closeExceptionsStream = close(generate(() -> closeable).limit(5).toArray(AutoCloseable[]::new)); verifyNoInteractions(closeable); List closeExceptions = closeExceptionsStream.collect(toList()); - assertThat(closeExceptions, Matchers.contains(asList(instanceOf(IOException.class), instanceOf(IllegalStateException.class)))); + assertThat(closeExceptions, contains(asList(instanceOf(IOException.class), instanceOf(IllegalStateException.class)))); verify(closeable, times(5)).close(); verifyNoMoreInteractions(closeable); } + @Nested + @Timeout(4) + class ForceOnAll { + + @Test + void runsOperationOnMultipleElements() { + List consumed = new ArrayList<>(); + List exceptions = forceOnAll(consumed::add, 1, 2, 3).collect(toList()); + assertThat(consumed, contains(1, 2, 3)); + assertThat(exceptions, empty()); + } + + @Test + void exceptionsFromOperationAreCollected() { + List onlyEvenNumbers = new ArrayList<>(); + List exceptions = forceOnAll(i -> { + if (i % 2 != 0) throw new IllegalArgumentException(i + " is odd!"); + onlyEvenNumbers.add(i); + }, 1, 2, 3, 4).collect(toList()); + assertThat(onlyEvenNumbers, contains(2, 4)); + assertThat(exceptions, contains( + where(Throwable::getMessage, is("1 is odd!")), + where(Throwable::getMessage, is("3 is odd!")))); + } + + @Test + void exceptionsFromTraversingStreamIsCollected() { + List consumed = new ArrayList<>(); + List exceptions = forceOnAll(consumed::add, + iterate(2, num -> num - 1).limit(5).mapToDouble(denominator -> 2 / denominator).boxed()) + .collect(toList()); + + assertAll( + () -> assertThat(exceptions, contains(isA(ArithmeticException.class))), + () -> assertThat(consumed, contains(1.0, 2.0, -2.0, -1.0))); + } + + @Test + void allElementsResolvedFromStreamException() { + List exceptions = forceOnAll(e -> fail("action should never be invoked"), + rangeClosed(1, 10).mapToObj(String::valueOf).map(s -> { throw new IllegalStateException(s); })) + .collect(toList()); + + assertThat(exceptions, hasSize(10)); + } + + @Test + void lastElementsResolvedFromStreamException() { + List consumed = new ArrayList<>(); + List exceptions = forceOnAll(consumed::add, + iterate(2, i -> i + -1).limit(3).mapToObj(denominator -> 2 / denominator)) + .collect(toList()); + assertAll( + () -> assertThat(exceptions, contains(isA(ArithmeticException.class))), + () -> assertThat(consumed, contains(1, 2))); + } + + @Test + void worksWithParalellStreams() { + AtomicLong successes = new AtomicLong(); + long failures = forceOnAll(__ -> successes.incrementAndGet(), + iterate(0, i -> i + 1).limit(100_000).parallel() + .map(i -> i % 4) // 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ... + .map(denominator -> 4 / denominator) // Fails with div by zero 1/4 of the times + .boxed()) + .count(); + + assertThat("3 times as much successes as failures: " + successes + " / " + failures, + failures * 3, is(successes.get())); + } + + @Test + void doesNotInvokeOperationOnNulls(@Mock AutoCloseable resource) throws Exception { + Optional exception = forceOnAll(AutoCloseable::close, resource, null, resource, null, resource) + .collect(toSingleExceptionWithSuppressed()) + .map(DiggExceptions::asUnchecked); + assertAll( + () -> verify(resource, times(3)).close(), + () -> assertDoesNotThrow(() -> exception.ifPresent(e -> { throw e; }))); + } + + } + } diff --git a/src/test/java/no/digipost/DiggCollectorsTest.java b/src/test/java/no/digipost/DiggCollectorsTest.java index 49cac1e..2b70343 100644 --- a/src/test/java/no/digipost/DiggCollectorsTest.java +++ b/src/test/java/no/digipost/DiggCollectorsTest.java @@ -20,6 +20,9 @@ import no.digipost.tuple.ViewableAsTuple; import no.digipost.util.ViewableAsOptional; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.quicktheories.core.Gen; import uk.co.probablyfine.matchers.OptionalMatchers; @@ -53,7 +56,6 @@ import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; import static org.quicktheories.QuickTheory.qt; import static org.quicktheories.generators.SourceDSL.lists; import static org.quicktheories.generators.SourceDSL.strings; @@ -61,12 +63,13 @@ import static uk.co.probablyfine.matchers.Java8Matchers.whereNot; import static uk.co.probablyfine.matchers.OptionalMatchers.contains; -public class DiggCollectorsTest { +@ExtendWith(MockitoExtension.class) +class DiggCollectorsTest { interface NumTranslation extends ViewableAsTuple> {} @Test - public void collectTuplesIntoMap() { + void collectTuplesIntoMap() { NumTranslation oneInEnglish = () -> Tuple.of(1, Optional.of("one")); NumTranslation twoInEnglish = () -> Tuple.of(2, Optional.of("two")); NumTranslation twoInSpanish = () -> Tuple.of(2, Optional.of("dos")); @@ -83,7 +86,7 @@ public void collectTuplesIntoMap() { } @Test - public void collectMultitupleFromTuplesWithEqualFirstElement() { + void collectMultitupleFromTuplesWithEqualFirstElement() { NumTranslation oneInEnglish = () -> Tuple.of(1, Optional.of("one")); NumTranslation oneInSpanish = () -> Tuple.of(1, Optional.of("uno")); NumTranslation oneInEsperanto = () -> Tuple.of(1, Optional.of("unu")); @@ -96,13 +99,13 @@ public void collectMultitupleFromTuplesWithEqualFirstElement() { } @Test - public void collectNoTuplesYieldsEmptyOptional() { + void collectNoTuplesYieldsEmptyOptional() { Optional>> noTuple = Stream.>>empty().collect(toMultituple()); assertThat(noTuple, OptionalMatchers.empty()); } @Test - public void collectMultitupleFromTuplesWithNonDistinctFirstElementIsErroneous() { + void collectMultitupleFromTuplesWithNonDistinctFirstElementIsErroneous() { NumTranslation oneInEnglish = () -> Tuple.of(1, Optional.of("one")); NumTranslation missingOne = () -> Tuple.of(1, Optional.empty()); NumTranslation oneInEsperanto = () -> Tuple.of(1, Optional.of("unu")); @@ -113,20 +116,20 @@ public void collectMultitupleFromTuplesWithNonDistinctFirstElementIsErroneous() } @Test - public void collectTheValueOfSingleElementStream() { + void collectTheValueOfSingleElementStream() { assertThat(Stream.of(42).collect(allowAtMostOne()), contains(is(42))); assertThat(Stream.empty().collect(allowAtMostOne()), OptionalMatchers.empty()); assertThat(Stream.of((String) null).collect(allowAtMostOne()), OptionalMatchers.empty()); } @Test - public void allowAtMostOneFailsEvenIfExcessiveElementsAreNull() { + void allowAtMostOneFailsEvenIfExcessiveElementsAreNull() { Stream xAndNull = Stream.of("x", null); assertThrows(ViewableAsOptional.TooManyElements.class, () -> xAndNull.collect(allowAtMostOne())); } @Test - public void convertTwoExceptionsToSingleWithSuppressed() { + void convertTwoExceptionsToSingleWithSuppressed() { Exception mainException = new Exception("main"); Exception suppressedException = new Exception("suppressed"); Exception collatedException = Stream.of(mainException, suppressedException).collect(toSingleExceptionWithSuppressed()).get(); @@ -135,7 +138,7 @@ public void convertTwoExceptionsToSingleWithSuppressed() { } @Test - public void convertLotsOfExceptionsToSingleWithTheRestSuppressed() { + void convertLotsOfExceptionsToSingleWithTheRestSuppressed() { Stream exceptions = range(0, 300).mapToObj(n -> new Exception("exception-" + n)); Exception collatedException = exceptions.parallel().collect(toSingleExceptionWithSuppressed()).get(); assertThat(collatedException, where(Throwable::getSuppressed, arrayWithSize(299))); @@ -143,7 +146,7 @@ public void convertLotsOfExceptionsToSingleWithTheRestSuppressed() { } @Test - public void addLotsOfSuppressedToGivenException() { + void addLotsOfSuppressedToGivenException() { Stream exceptions = range(0, 300).mapToObj(n -> new Exception("exception-" + n)); IOException collatedException = exceptions.parallel().collect(asSuppressedExceptionsOf(new IOException())); assertThat(collatedException, where(Throwable::getSuppressed, arrayWithSize(300))); @@ -151,9 +154,7 @@ public void addLotsOfSuppressedToGivenException() { } @Test - public void suppressesExceptionsCorrectlyWithTryCatchBlocks() throws SQLException { - Connection connection = mock(Connection.class); - PreparedStatement pstmt = mock(PreparedStatement.class); + void suppressesExceptionsCorrectlyWithTryCatchBlocks(@Mock Connection connection, @Mock PreparedStatement pstmt) throws SQLException { SQLException connectionCloseException = new SQLException(); BatchUpdateException statementCloseException = new BatchUpdateException(); doThrow(connectionCloseException).when(connection).close(); @@ -170,7 +171,7 @@ public void suppressesExceptionsCorrectlyWithTryCatchBlocks() throws SQLExceptio } @Test - public void convertNoExceptionsToEmptyOptional() { + void convertNoExceptionsToEmptyOptional() { Optional noException = range(0, 300).parallel().mapToObj(n -> new Exception("exception-" + n)).filter(e -> false).collect(toSingleExceptionWithSuppressed()); assertThat(noException, whereNot(Optional::isPresent)); } @@ -179,7 +180,7 @@ public void convertNoExceptionsToEmptyOptional() { private final Gen> listsWithAtLeastTwoElements = lists().of(strings().allPossible().ofLengthBetween(0, 10)).ofSizeBetween(2, 40); @Test - public void allowAtMostOneFails() { + void allowAtMostOneFails() { qt() .forAll(listsWithAtLeastTwoElements) .check(list -> { @@ -193,7 +194,7 @@ public void allowAtMostOneFails() { } @Test - public void allowAtMostOneFailsWithCustomException() { + void allowAtMostOneFailsWithCustomException() { IllegalStateException customException = new IllegalStateException(); qt() .forAll(listsWithAtLeastTwoElements) diff --git a/src/test/java/no/digipost/DiggEnumsTest.java b/src/test/java/no/digipost/DiggEnumsTest.java index 91e7492..7f395e5 100644 --- a/src/test/java/no/digipost/DiggEnumsTest.java +++ b/src/test/java/no/digipost/DiggEnumsTest.java @@ -54,7 +54,7 @@ public void convertFromCommaSeparatedListOfEnumNames() { qt() .forAll(multipleEnums) - .asWithPrecursor(enums -> Stream.of(enums).map(Enum::name).collect(joining(" , ", " ", " "))) + .asWithPrecursor(enums -> Stream.of(enums).map(MyEnum::name).collect(joining(" , ", " ", " "))) .checkAssert((enums, commaSeparatedNames) -> assertThat(fromCommaSeparatedNames(commaSeparatedNames, MyEnum.class), contains(enums))); } @@ -74,11 +74,11 @@ public void convertToStringOfDelimiterSeparatedStrings() { public void toStringConversionsAreSpecialCasesOfTheGenericBaseCase() { qt() .forAll(multipleEnums) - .check(enums -> toCommaSeparatedNames(enums).equals(toStringOf(Enum::name, joining(","), enums))); + .check(enums -> toCommaSeparatedNames(enums).equals(toStringOf(MyEnum::name, joining(","), enums))); qt() .forAll(multipleEnums) - .check(enums -> toNames(": ", enums).equals(toStringOf(Enum::name, joining(": "), enums))); + .check(enums -> toNames(": ", enums).equals(toStringOf(MyEnum::name, joining(": "), enums))); qt() .forAll(multipleEnums) diff --git a/src/test/java/no/digipost/DiggExceptionsTest.java b/src/test/java/no/digipost/DiggExceptionsTest.java index 2d08f8a..007b000 100644 --- a/src/test/java/no/digipost/DiggExceptionsTest.java +++ b/src/test/java/no/digipost/DiggExceptionsTest.java @@ -18,6 +18,9 @@ import no.digipost.concurrent.OneTimeToggle; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.io.UncheckedIOException; @@ -42,25 +45,25 @@ import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static uk.co.probablyfine.matchers.Java8Matchers.where; -public class DiggExceptionsTest { +@ExtendWith(MockitoExtension.class) +class DiggExceptionsTest { @Test - public void causalChainOfNullIsEmptyStream() { + void causalChainOfNullIsEmptyStream() { assertThat(causalChainOf(null).collect(toList()), empty()); } @Test - public void returnsTheCausalChainOfExceptions() { + void returnsTheCausalChainOfExceptions() { List exception = causalChainOf(new Exception(new IllegalStateException(new IOException()))).collect(toList()); assertThat(exception, contains(instanceOf(Exception.class), instanceOf(IllegalStateException.class), instanceOf(IOException.class))); } @Test - public void runAThrowingRunnableUnchecked() { + void runAThrowingRunnableUnchecked() { OneTimeToggle toggled = new OneTimeToggle(); DiggExceptions.runUnchecked(() -> toggled.nowOrIfAlreadyThenThrow(() -> new AssertionError("should not be run twice!"))); assertThat(toggled, where(OneTimeToggle::yet)); @@ -70,7 +73,7 @@ public void runAThrowingRunnableUnchecked() { } @Test - public void getAThrowingSupplierUnchecked() { + void getAThrowingSupplierUnchecked() { assertThat(getUnchecked(() -> 42), is(42)); Exception e = new Exception(); @@ -78,7 +81,7 @@ public void getAThrowingSupplierUnchecked() { } @Test - public void applyAThrowingFunctionUnchecked() { + void applyAThrowingFunctionUnchecked() { assertThat(applyUnchecked(Math::round, 4.6f), is(5)); assertThat(applyUnchecked(n -> n, null), nullValue()); assertThat(applyUnchecked(n -> n, Optional.empty()), is(Optional.empty())); @@ -88,14 +91,12 @@ public void applyAThrowingFunctionUnchecked() { } @Test - public void factoryMethodsForThrowingFunctionalInterfaces() throws Throwable { + void factoryMethodsForThrowingFunctionalInterfaces(@Mock Consumer exceptionHandler) throws Throwable { assertThat(mayThrow((t) -> t).apply("a"), is("a")); assertThat(mayThrow((t, u) -> t).apply("a", "b"), is("a")); assertThat(mayThrow(() -> "a").get(), is("a")); Exception ex = new Exception(); - @SuppressWarnings("unchecked") - Consumer exceptionHandler = mock(Consumer.class); mayThrow(t -> { if (t == null) throw ex; }).ifException(exceptionHandler).accept(null); verify(exceptionHandler).accept(ex); diff --git a/src/test/java/no/digipost/DiggIOTest.java b/src/test/java/no/digipost/DiggIOTest.java index fe4ac2a..5bc0a0f 100644 --- a/src/test/java/no/digipost/DiggIOTest.java +++ b/src/test/java/no/digipost/DiggIOTest.java @@ -17,27 +17,28 @@ import no.digipost.function.ThrowingConsumer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.function.Consumer; import static no.digipost.DiggIO.autoClosing; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class DiggIOTest { +@ExtendWith(MockitoExtension.class) +class DiggIOTest { @Test - public void closesResourceAfterSuccess() throws Exception { - AutoCloseable resource = mock(AutoCloseable.class); + void closesResourceAfterSuccess(@Mock AutoCloseable resource) throws Exception { autoClosing(r -> {}).accept(resource); verify(resource, times(1)).close(); } @Test - public void closesResourceAfterFailure() throws Exception { - AutoCloseable resource = mock(AutoCloseable.class); + void closesResourceAfterFailure(@Mock AutoCloseable resource) throws Exception { Consumer autoClosingConsumer = autoClosing((ThrowingConsumer) r -> { throw new Exception(); }); assertThrows(RuntimeException.class, () -> autoClosingConsumer.accept(resource)); verify(resource, times(1)).close(); diff --git a/src/test/java/no/digipost/DiggStreamsTest.java b/src/test/java/no/digipost/DiggStreamsTest.java index bda1f05..f9b639d 100644 --- a/src/test/java/no/digipost/DiggStreamsTest.java +++ b/src/test/java/no/digipost/DiggStreamsTest.java @@ -15,6 +15,7 @@ */ package no.digipost; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.quicktheories.WithQuickTheories; import org.quicktheories.core.Gen; @@ -22,26 +23,36 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Spliterators.AbstractSpliterator; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; -import static uk.co.probablyfine.matchers.StreamMatchers.contains; -import static uk.co.probablyfine.matchers.StreamMatchers.equalTo; +import static java.lang.Integer.toBinaryString; import static java.util.Arrays.asList; +import static java.util.Spliterator.DISTINCT; +import static java.util.Spliterator.NONNULL; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.IntStream.iterate; +import static no.digipost.DiggBase.friendlyName; import static no.digipost.DiggStreams.streamByIntIndex; import static no.digipost.DiggStreams.streamByKey; import static no.digipost.DiggStreams.streamByLongIndex; import static no.digipost.DiggStreams.streamWhileNonEmpty; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.is; +import static uk.co.probablyfine.matchers.Java8Matchers.where; +import static uk.co.probablyfine.matchers.StreamMatchers.contains; +import static uk.co.probablyfine.matchers.StreamMatchers.equalTo; -public class DiggStreamsTest implements WithQuickTheories { +class DiggStreamsTest implements WithQuickTheories { @Test - public void streamAllElementsByIntIndex() { + void streamAllElementsByIntIndex() { Gen> listsOfStrings = lists().of(strings().allPossible().ofLengthBetween(0, 100)).ofSizeBetween(0, 20); qt() .forAll(listsOfStrings) @@ -49,14 +60,14 @@ public void streamAllElementsByIntIndex() { } @Test - public void streamValuesOfMap() { + void streamValuesOfMap() { List chars = asList('A', 'B', 'C', 'D'); Map charAndString = chars.stream().collect(toMap(Function.identity(), String::valueOf)); assertThat(streamByKey(charAndString, chars.stream(), Map::get), contains("A", "B", "C", "D")); } @Test - public void streamValuesByLongIndex() { + void streamValuesByLongIndex() { assertThat(streamByLongIndex(new LongConverter(), 4, LongConverter::asString), contains("0", "1", "2", "3")); } @@ -67,7 +78,7 @@ String asString(long value) { } @Test - public void streamPagesWhilePageHasContent() { + void streamPagesWhilePageHasContent() { Gen alphabetLengths = integers().between(10, 200); Gen pageSizes = integers().between(1, 13); @@ -77,6 +88,41 @@ public void streamPagesWhilePageHasContent() { .checkAssert(pagedAlphabet -> assertThat(streamWhileNonEmpty(pagedAlphabet::getPage), equalTo(pagedAlphabet.getEntireAlphabet()))); } + @Nested + class Spliterators { + final class CharacteristicsTester extends AbstractSpliterator { + public CharacteristicsTester(int characteristics) { + super(Long.MAX_VALUE, characteristics); + } + @Override + public boolean tryAdvance(Consumer action) { + return false; + } + } + @Test + void describeNoCharacteristics() { + assertThat(new CharacteristicsTester(0), where(DiggStreams::describeCharacteristics, allOf( + containsString(friendlyName(CharacteristicsTester.class)), + containsStringIgnoringCase("no enabled characteristics")))); + } + + @Test + void describeOneEnabledCharacteristic() { + assertThat(new CharacteristicsTester(NONNULL), where(DiggStreams::describeCharacteristics, allOf( + containsString(friendlyName(CharacteristicsTester.class)), + containsStringIgnoringCase("non-null"), + containsString(toBinaryString(NONNULL))))); + } + + @Test + void describeMultipleEnabledCharacteristics() { + assertThat(new CharacteristicsTester(NONNULL | DISTINCT), where(DiggStreams::describeCharacteristics, allOf( + containsString(friendlyName(CharacteristicsTester.class)), + containsStringIgnoringCase("non-null"), containsStringIgnoringCase("distinct"), + containsString(toBinaryString(DISTINCT | NONNULL))))); + } + } + private static final class PagedAlphabet { final char firstChar = 'A'; final int length; diff --git a/src/test/java/no/digipost/collection/BatchedIterableTest.java b/src/test/java/no/digipost/collection/BatchedIterableTest.java index 7e5b46e..8bec45a 100644 --- a/src/test/java/no/digipost/collection/BatchedIterableTest.java +++ b/src/test/java/no/digipost/collection/BatchedIterableTest.java @@ -36,11 +36,13 @@ class BatchedIterableTest { @Test void emptyBatch() { - Batches empty1 = new Batches<>(); + @SuppressWarnings("unchecked") + Batches empty1 = new Batches<>(); assertThat(batched(empty1, b -> false), is(emptyIterable())); assertThat(empty1.fetches(), is(1)); - Batches empty2 = new Batches<>(); + @SuppressWarnings("unchecked") + Batches empty2 = new Batches<>(); assertThat(batched(empty2, b -> true), is(emptyIterable())); assertThat(empty2.fetches(), is(2)); } diff --git a/src/test/java/no/digipost/function/ThrowingFunctionTest.java b/src/test/java/no/digipost/function/ThrowingFunctionTest.java index d690abe..d6f8c30 100644 --- a/src/test/java/no/digipost/function/ThrowingFunctionTest.java +++ b/src/test/java/no/digipost/function/ThrowingFunctionTest.java @@ -16,6 +16,9 @@ package no.digipost.function; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -26,10 +29,10 @@ import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +@ExtendWith(MockitoExtension.class) public class ThrowingFunctionTest { private final RuntimeException ex = new RuntimeException("fail"); @@ -55,16 +58,15 @@ public void rethrowOriginalError() { } @Test - public void translateToEmptyOptionalAndDelegateExceptionToHandler() { + public void translateToEmptyOptionalAndDelegateExceptionToHandler( + @Mock Consumer getException, + @Mock BiConsumer getValueAndException) { + ThrowingFunction fn = i -> {throw ex;}; - @SuppressWarnings("unchecked") - Consumer getException = mock(Consumer.class); assertThat(fn.ifException(getException).apply(42), is(empty())); verify(getException, times(1)).accept(ex); - @SuppressWarnings("unchecked") - BiConsumer getValueAndException = mock(BiConsumer.class); assertThat(fn.ifException(getValueAndException).apply(42), is(empty())); verify(getValueAndException, times(1)).accept(42, ex); } diff --git a/src/test/java/no/digipost/function/ThrowingRunnableTest.java b/src/test/java/no/digipost/function/ThrowingRunnableTest.java index 0cd4e04..bdf30a8 100644 --- a/src/test/java/no/digipost/function/ThrowingRunnableTest.java +++ b/src/test/java/no/digipost/function/ThrowingRunnableTest.java @@ -16,16 +16,19 @@ package no.digipost.function; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.function.Consumer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +@ExtendWith(MockitoExtension.class) public class ThrowingRunnableTest { private final RuntimeException ex = new RuntimeException(); @@ -45,10 +48,8 @@ public void rethrowOriginalError() { } @Test - public void translateToEmptyOptionalAndDelegateExceptionToHandler() { + public void translateToEmptyOptionalAndDelegateExceptionToHandler(@Mock Consumer handler) { ThrowingRunnable fn = () -> {throw ex;}; - @SuppressWarnings("unchecked") - Consumer handler = mock(Consumer.class); fn.ifException(handler).run(); verify(handler, times(1)).accept(ex); diff --git a/src/test/java/no/digipost/function/ThrowingSupplierTest.java b/src/test/java/no/digipost/function/ThrowingSupplierTest.java index a139503..4d83101 100644 --- a/src/test/java/no/digipost/function/ThrowingSupplierTest.java +++ b/src/test/java/no/digipost/function/ThrowingSupplierTest.java @@ -16,6 +16,9 @@ package no.digipost.function; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.function.Consumer; import java.util.function.Supplier; @@ -26,11 +29,11 @@ import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static uk.co.probablyfine.matchers.Java8Matchers.where; +@ExtendWith(MockitoExtension.class) class ThrowingSupplierTest { private final RuntimeException ex = new RuntimeException(); @@ -57,10 +60,8 @@ void rethrowOriginalError() { } @Test - void translateToEmptyOptionalAndDelegateExceptionToHandler() { + void translateToEmptyOptionalAndDelegateExceptionToHandler(@Mock Consumer handler) { ThrowingSupplier fn = () -> {throw ex;}; - @SuppressWarnings("unchecked") - Consumer handler = mock(Consumer.class); assertThat(fn.ifException(handler).get(), is(empty())); verify(handler, times(1)).accept(ex); diff --git a/src/test/java/no/digipost/io/InputStreamIteratorTest.java b/src/test/java/no/digipost/io/InputStreamIteratorTest.java index 060520c..0524a29 100644 --- a/src/test/java/no/digipost/io/InputStreamIteratorTest.java +++ b/src/test/java/no/digipost/io/InputStreamIteratorTest.java @@ -71,7 +71,7 @@ void throws_if_next_is_called_with_no_more_elements() throws Exception { InputStreamIterator iterator = new InputStreamIterator(inputStream, 2); assertThat(consumeToString(iterator, UTF_8), is("Some data")); - assertThat(iterator, whereNot(Iterator::hasNext)); + assertThat(iterator, whereNot(Iterator::hasNext)); assertThrows(NoSuchElementException.class, iterator::next); }