Procházet zdrojové kódy

Tests for AutoClosingStream

Markus Kreusch před 9 roky
rodič
revize
d14c81d066

+ 0 - 10
main/commons/src/main/java/org/cryptomator/common/streams/AutoClosingDoubleStream.java

@@ -11,7 +11,6 @@ import java.util.function.DoublePredicate;
 import java.util.function.ObjDoubleConsumer;
 import java.util.function.Supplier;
 import java.util.stream.DoubleStream;
-import java.util.stream.Stream;
 
 public class AutoClosingDoubleStream extends DelegatingDoubleStream {
 
@@ -176,13 +175,4 @@ public class AutoClosingDoubleStream extends DelegatingDoubleStream {
 		}
 	}
 
-	@Override
-	public Stream<Double> boxed() {
-		try {
-			return super.boxed();
-		} finally {
-			close();
-		}
-	}
-
 }

+ 0 - 29
main/commons/src/main/java/org/cryptomator/common/streams/DelegatingStreamFactory.java

@@ -1,6 +1,5 @@
 package org.cryptomator.common.streams;
 
-import java.util.function.Function;
 import java.util.stream.DoubleStream;
 import java.util.stream.IntStream;
 import java.util.stream.LongStream;
@@ -8,34 +7,6 @@ import java.util.stream.Stream;
 
 public interface DelegatingStreamFactory {
 
-	public static DelegatingStreamFactory of( //
-			ObjectStreamWrapper objectStreamWrapper, //
-			Function<IntStream, IntStream> intStreamWrapper, //
-			Function<LongStream, LongStream> longStreamWrapper, //
-			Function<DoubleStream, DoubleStream> doubleStreamWrapper) {
-		return new DelegatingStreamFactory() {
-			@Override
-			public DoubleStream from(DoubleStream other) {
-				return doubleStreamWrapper.apply(other);
-			}
-
-			@Override
-			public LongStream from(LongStream other) {
-				return longStreamWrapper.apply(other);
-			}
-
-			@Override
-			public IntStream from(IntStream other) {
-				return intStreamWrapper.apply(other);
-			}
-
-			@Override
-			public <S> Stream<S> from(Stream<S> other) {
-				return objectStreamWrapper.from(other);
-			}
-		};
-	}
-
 	<S> Stream<S> from(Stream<S> other);
 
 	IntStream from(IntStream other);

+ 227 - 0
main/commons/src/test/java/org/cryptomator/common/streams/AutoClosingDoubleStreamTest.java

@@ -0,0 +1,227 @@
+package org.cryptomator.common.streams;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.DoubleSummaryStatistics;
+import java.util.List;
+import java.util.OptionalDouble;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleConsumer;
+import java.util.function.DoubleFunction;
+import java.util.function.DoublePredicate;
+import java.util.function.DoubleToIntFunction;
+import java.util.function.DoubleToLongFunction;
+import java.util.function.DoubleUnaryOperator;
+import java.util.function.Function;
+import java.util.function.ObjDoubleConsumer;
+import java.util.function.Supplier;
+import java.util.stream.BaseStream;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+@RunWith(Theories.class)
+public class AutoClosingDoubleStreamTest {
+
+	private static final DoublePredicate A_DOUBLE_PREDICATE = any -> true;
+	private static final DoubleFunction A_DOUBLE_FUNCTION = i -> null;
+	private static final BiConsumer A_BICONSUMER = (a, b) -> {
+	};
+	private static final Supplier A_SUPPLIER = () -> null;
+
+	@DataPoints("intermediateOperations")
+	public static final List<DoubleermediateOperation<?>> INTERMEDIATE_OPERATIONS = new ArrayList<>();
+
+	@DataPoints("terminalOperations")
+	public static final List<TerminalOperation<?>> TERMINAL_OPERATIONS = new ArrayList<>();
+	private static final DoubleUnaryOperator A_DOUBLE_UNARY_OPERATOR = i -> 3;
+	private static final DoubleToLongFunction A_DOUBLE_TO_LONG_FUNCTION = i -> 3L;
+	private static final DoubleToIntFunction A_DOUBLE_TO_INT_FUNCTION = i -> 5;
+	private static final DoubleConsumer A_DOUBLE_CONSUMER = i -> {
+	};
+	private static final ObjDoubleConsumer AN_OBJ_DOUBLE_CONSUMER = (a, b) -> {
+	};
+	private static final DoubleBinaryOperator A_DOUBLE_BINARY_OPERATOR = (a, b) -> a;
+
+	static {
+		// define intermediate operations
+		test(DoubleStream.class, DoubleStream::distinct);
+		test(DoubleStream.class, stream -> stream.filter(A_DOUBLE_PREDICATE));
+		test(DoubleStream.class, stream -> stream.flatMap(A_DOUBLE_FUNCTION));
+		test(DoubleStream.class, stream -> stream.limit(5));
+		test(DoubleStream.class, stream -> stream.map(A_DOUBLE_UNARY_OPERATOR));
+		test(LongStream.class, stream -> stream.mapToLong(A_DOUBLE_TO_LONG_FUNCTION));
+		test(Stream.class, stream -> stream.mapToObj(A_DOUBLE_FUNCTION));
+		test(IntStream.class, stream -> stream.mapToInt(A_DOUBLE_TO_INT_FUNCTION));
+		test(DoubleStream.class, DoubleStream::parallel);
+		test(DoubleStream.class, stream -> stream.peek(A_DOUBLE_CONSUMER));
+		test(DoubleStream.class, DoubleStream::sequential);
+		test(DoubleStream.class, stream -> stream.skip(5));
+		test(DoubleStream.class, DoubleStream::sorted);
+		test(DoubleStream.class, DoubleStream::unordered);
+		test(Stream.class, DoubleStream::boxed);
+
+		// define terminal operations
+		test(stream -> stream.allMatch(A_DOUBLE_PREDICATE), true);
+		test(stream -> stream.anyMatch(A_DOUBLE_PREDICATE), true);
+		test(stream -> stream.collect(A_SUPPLIER, AN_OBJ_DOUBLE_CONSUMER, A_BICONSUMER), 7d);
+		test(DoubleStream::count, 3L);
+		test(DoubleStream::findAny, OptionalDouble.of(3));
+		test(DoubleStream::findFirst, OptionalDouble.of(3));
+		test(stream -> stream.forEach(A_DOUBLE_CONSUMER));
+		test(stream -> stream.forEachOrdered(A_DOUBLE_CONSUMER));
+		test(stream -> stream.max(), OptionalDouble.of(3));
+		test(stream -> stream.min(), OptionalDouble.of(3));
+		test(stream -> stream.noneMatch(A_DOUBLE_PREDICATE), true);
+		test(stream -> stream.reduce(A_DOUBLE_BINARY_OPERATOR), OptionalDouble.of(3));
+		test(stream -> stream.reduce(1, A_DOUBLE_BINARY_OPERATOR), 3d);
+		test(DoubleStream::toArray, new double[1]);
+		test(DoubleStream::sum, 1d);
+		test(DoubleStream::average, OptionalDouble.of(3d));
+		test(DoubleStream::summaryStatistics, new DoubleSummaryStatistics());
+	}
+
+	private static <T> void test(Consumer<DoubleStream> consumer) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return null;
+			}
+
+			@Override
+			public T apply(DoubleStream stream) {
+				consumer.accept(stream);
+				return null;
+			}
+		});
+	}
+
+	private static <T> void test(Function<DoubleStream, T> function, T result) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return result;
+			}
+
+			@Override
+			public T apply(DoubleStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	private static <T extends BaseStream> void test(Class<? extends T> type, Function<DoubleStream, T> function) {
+		INTERMEDIATE_OPERATIONS.add(new DoubleermediateOperation<T>() {
+			@Override
+			public Class<? extends T> type() {
+				return type;
+			}
+
+			@Override
+			public T apply(DoubleStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	private DoubleStream delegate;
+	private DoubleStream inTest;
+
+	@Before
+	public void setUp() {
+		delegate = mock(DoubleStream.class);
+		inTest = AutoClosingDoubleStream.from(delegate);
+	}
+
+	@Theory
+	public void testIntermediateOperationReturnsNewAutoClosingStream(@FromDataPoints("intermediateOperations") DoubleermediateOperation intermediateOperation) {
+		BaseStream newDelegate = (BaseStream) mock(intermediateOperation.type());
+		when(intermediateOperation.apply(delegate)).thenReturn(newDelegate);
+
+		BaseStream result = intermediateOperation.apply(inTest);
+
+		assertThat(result, isAutoClosing());
+		verifyDelegate(result, newDelegate);
+	}
+
+	@Theory
+	public void testTerminalOperationDelegatesToAndClosesDelegate(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		Object expectedResult = terminalOperation.result();
+		if (expectedResult != null) {
+			when(terminalOperation.apply(delegate)).thenReturn(expectedResult);
+		}
+
+		Object result = terminalOperation.apply(inTest);
+
+		InOrder inOrder = inOrder(delegate);
+		assertThat(result, is(expectedResult));
+		inOrder.verify(delegate).close();
+	}
+
+	@Theory
+	public void testTerminalOperationClosesDelegateEvenOnException(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		RuntimeException exception = new RuntimeException();
+		terminalOperation.apply(doThrow(exception).when(delegate));
+
+		thrown.expect(is(exception));
+
+		try {
+			terminalOperation.apply(inTest);
+		} finally {
+			verify(delegate).close();
+		}
+	}
+
+	private Matcher<BaseStream> isAutoClosing() {
+		return is(anyOf(instanceOf(AutoClosingStream.class), instanceOf(AutoClosingDoubleStream.class), instanceOf(AutoClosingIntStream.class), instanceOf(AutoClosingLongStream.class)));
+	}
+
+	private void verifyDelegate(BaseStream result, BaseStream newDelegate) {
+		result.close();
+		verify(newDelegate).close();
+	}
+
+	private interface TerminalOperation<T> {
+
+		T result();
+
+		T apply(DoubleStream stream);
+
+	}
+
+	private interface DoubleermediateOperation<T extends BaseStream> {
+
+		Class<? extends T> type();
+
+		T apply(DoubleStream stream);
+
+	}
+
+}

+ 228 - 0
main/commons/src/test/java/org/cryptomator/common/streams/AutoClosingIntStreamTest.java

@@ -0,0 +1,228 @@
+package org.cryptomator.common.streams;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.IntSummaryStatistics;
+import java.util.List;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntBinaryOperator;
+import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
+import java.util.function.IntPredicate;
+import java.util.function.IntToDoubleFunction;
+import java.util.function.IntToLongFunction;
+import java.util.function.IntUnaryOperator;
+import java.util.function.ObjIntConsumer;
+import java.util.function.Supplier;
+import java.util.stream.BaseStream;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+@RunWith(Theories.class)
+public class AutoClosingIntStreamTest {
+
+	private static final IntPredicate AN_INT_PREDICATE = any -> true;
+	private static final IntFunction AN_INT_FUNCTION = i -> null;
+	private static final BiConsumer A_BICONSUMER = (a, b) -> {
+	};
+	private static final Supplier A_SUPPLIER = () -> null;
+
+	@DataPoints("intermediateOperations")
+	public static final List<IntermediateOperation<?>> INTERMEDIATE_OPERATIONS = new ArrayList<>();
+
+	@DataPoints("terminalOperations")
+	public static final List<TerminalOperation<?>> TERMINAL_OPERATIONS = new ArrayList<>();
+	private static final IntUnaryOperator AN_INT_UNARY_OPERATOR = i -> 3;
+	private static final IntToDoubleFunction AN_INT_TO_DOUBLE_FUNCTION = i -> 3d;
+	private static final IntToLongFunction AN_INT_TO_LONG_FUNCTION = i -> 5L;
+	private static final IntConsumer AN_INT_CONSUMER = i -> {
+	};
+	private static final ObjIntConsumer AN_OBJ_INT_CONSUMER = (a, b) -> {
+	};
+	private static final IntBinaryOperator AN_INT_BINARY_OPERATOR = (a, b) -> a;
+
+	static {
+		// define intermediate operations
+		test(IntStream.class, IntStream::distinct);
+		test(IntStream.class, stream -> stream.filter(AN_INT_PREDICATE));
+		test(IntStream.class, stream -> stream.flatMap(AN_INT_FUNCTION));
+		test(IntStream.class, stream -> stream.limit(5));
+		test(IntStream.class, stream -> stream.map(AN_INT_UNARY_OPERATOR));
+		test(DoubleStream.class, stream -> stream.mapToDouble(AN_INT_TO_DOUBLE_FUNCTION));
+		test(Stream.class, stream -> stream.mapToObj(AN_INT_FUNCTION));
+		test(LongStream.class, stream -> stream.mapToLong(AN_INT_TO_LONG_FUNCTION));
+		test(IntStream.class, IntStream::parallel);
+		test(IntStream.class, stream -> stream.peek(AN_INT_CONSUMER));
+		test(IntStream.class, IntStream::sequential);
+		test(IntStream.class, stream -> stream.skip(5));
+		test(IntStream.class, IntStream::sorted);
+		test(IntStream.class, IntStream::unordered);
+		test(Stream.class, IntStream::boxed);
+
+		// define terminal operations
+		test(stream -> stream.allMatch(AN_INT_PREDICATE), true);
+		test(stream -> stream.anyMatch(AN_INT_PREDICATE), true);
+		test(stream -> stream.collect(A_SUPPLIER, AN_OBJ_INT_CONSUMER, A_BICONSUMER), 7);
+		test(IntStream::count, 3L);
+		test(IntStream::findAny, OptionalInt.of(3));
+		test(IntStream::findFirst, OptionalInt.of(3));
+		test(stream -> stream.forEach(AN_INT_CONSUMER));
+		test(stream -> stream.forEachOrdered(AN_INT_CONSUMER));
+		test(stream -> stream.max(), OptionalInt.of(3));
+		test(stream -> stream.min(), OptionalInt.of(3));
+		test(stream -> stream.noneMatch(AN_INT_PREDICATE), true);
+		test(stream -> stream.reduce(AN_INT_BINARY_OPERATOR), OptionalInt.of(3));
+		test(stream -> stream.reduce(1, AN_INT_BINARY_OPERATOR), 3);
+		test(IntStream::toArray, new int[1]);
+		test(IntStream::sum, 1);
+		test(IntStream::average, OptionalDouble.of(3d));
+		test(IntStream::summaryStatistics, new IntSummaryStatistics());
+	}
+
+	private static <T> void test(Consumer<IntStream> consumer) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return null;
+			}
+
+			@Override
+			public T apply(IntStream stream) {
+				consumer.accept(stream);
+				return null;
+			}
+		});
+	}
+
+	private static <T> void test(Function<IntStream, T> function, T result) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return result;
+			}
+
+			@Override
+			public T apply(IntStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	private static <T extends BaseStream> void test(Class<? extends T> type, Function<IntStream, T> function) {
+		INTERMEDIATE_OPERATIONS.add(new IntermediateOperation<T>() {
+			@Override
+			public Class<? extends T> type() {
+				return type;
+			}
+
+			@Override
+			public T apply(IntStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	private IntStream delegate;
+	private IntStream inTest;
+
+	@Before
+	public void setUp() {
+		delegate = mock(IntStream.class);
+		inTest = AutoClosingIntStream.from(delegate);
+	}
+
+	@Theory
+	public void testIntermediateOperationReturnsNewAutoClosingStream(@FromDataPoints("intermediateOperations") IntermediateOperation intermediateOperation) {
+		BaseStream newDelegate = (BaseStream) mock(intermediateOperation.type());
+		when(intermediateOperation.apply(delegate)).thenReturn(newDelegate);
+
+		BaseStream result = intermediateOperation.apply(inTest);
+
+		assertThat(result, isAutoClosing());
+		verifyDelegate(result, newDelegate);
+	}
+
+	@Theory
+	public void testTerminalOperationDelegatesToAndClosesDelegate(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		Object expectedResult = terminalOperation.result();
+		if (expectedResult != null) {
+			when(terminalOperation.apply(delegate)).thenReturn(expectedResult);
+		}
+
+		Object result = terminalOperation.apply(inTest);
+
+		InOrder inOrder = inOrder(delegate);
+		assertThat(result, is(expectedResult));
+		inOrder.verify(delegate).close();
+	}
+
+	@Theory
+	public void testTerminalOperationClosesDelegateEvenOnException(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		RuntimeException exception = new RuntimeException();
+		terminalOperation.apply(doThrow(exception).when(delegate));
+
+		thrown.expect(is(exception));
+
+		try {
+			terminalOperation.apply(inTest);
+		} finally {
+			verify(delegate).close();
+		}
+	}
+
+	private Matcher<BaseStream> isAutoClosing() {
+		return is(anyOf(instanceOf(AutoClosingStream.class), instanceOf(AutoClosingDoubleStream.class), instanceOf(AutoClosingIntStream.class), instanceOf(AutoClosingLongStream.class)));
+	}
+
+	private void verifyDelegate(BaseStream result, BaseStream newDelegate) {
+		result.close();
+		verify(newDelegate).close();
+	}
+
+	private interface TerminalOperation<T> {
+
+		T result();
+
+		T apply(IntStream stream);
+
+	}
+
+	private interface IntermediateOperation<T extends BaseStream> {
+
+		Class<? extends T> type();
+
+		T apply(IntStream stream);
+
+	}
+
+}

+ 228 - 0
main/commons/src/test/java/org/cryptomator/common/streams/AutoClosingLongStreamTest.java

@@ -0,0 +1,228 @@
+package org.cryptomator.common.streams;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LongSummaryStatistics;
+import java.util.OptionalDouble;
+import java.util.OptionalLong;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.LongBinaryOperator;
+import java.util.function.LongConsumer;
+import java.util.function.LongFunction;
+import java.util.function.LongPredicate;
+import java.util.function.LongToDoubleFunction;
+import java.util.function.LongToIntFunction;
+import java.util.function.LongUnaryOperator;
+import java.util.function.ObjLongConsumer;
+import java.util.function.Supplier;
+import java.util.stream.BaseStream;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+@RunWith(Theories.class)
+public class AutoClosingLongStreamTest {
+
+	private static final LongPredicate AN_LONG_PREDICATE = any -> true;
+	private static final LongFunction AN_LONG_FUNCTION = i -> null;
+	private static final BiConsumer A_BICONSUMER = (a, b) -> {
+	};
+	private static final Supplier A_SUPPLIER = () -> null;
+
+	@DataPoints("intermediateOperations")
+	public static final List<LongermediateOperation<?>> INTERMEDIATE_OPERATIONS = new ArrayList<>();
+
+	@DataPoints("terminalOperations")
+	public static final List<TerminalOperation<?>> TERMINAL_OPERATIONS = new ArrayList<>();
+	private static final LongUnaryOperator AN_LONG_UNARY_OPERATOR = i -> 3;
+	private static final LongToDoubleFunction AN_LONG_TO_DOUBLE_FUNCTION = i -> 3d;
+	private static final LongToIntFunction AN_LONG_TO_INT_FUNCTION = i -> 5;
+	private static final LongConsumer AN_LONG_CONSUMER = i -> {
+	};
+	private static final ObjLongConsumer AN_OBJ_LONG_CONSUMER = (a, b) -> {
+	};
+	private static final LongBinaryOperator AN_LONG_BINARY_OPERATOR = (a, b) -> a;
+
+	static {
+		// define intermediate operations
+		test(LongStream.class, LongStream::distinct);
+		test(LongStream.class, stream -> stream.filter(AN_LONG_PREDICATE));
+		test(LongStream.class, stream -> stream.flatMap(AN_LONG_FUNCTION));
+		test(LongStream.class, stream -> stream.limit(5));
+		test(LongStream.class, stream -> stream.map(AN_LONG_UNARY_OPERATOR));
+		test(DoubleStream.class, stream -> stream.mapToDouble(AN_LONG_TO_DOUBLE_FUNCTION));
+		test(Stream.class, stream -> stream.mapToObj(AN_LONG_FUNCTION));
+		test(IntStream.class, stream -> stream.mapToInt(AN_LONG_TO_INT_FUNCTION));
+		test(LongStream.class, LongStream::parallel);
+		test(LongStream.class, stream -> stream.peek(AN_LONG_CONSUMER));
+		test(LongStream.class, LongStream::sequential);
+		test(LongStream.class, stream -> stream.skip(5));
+		test(LongStream.class, LongStream::sorted);
+		test(LongStream.class, LongStream::unordered);
+		test(Stream.class, LongStream::boxed);
+
+		// define terminal operations
+		test(stream -> stream.allMatch(AN_LONG_PREDICATE), true);
+		test(stream -> stream.anyMatch(AN_LONG_PREDICATE), true);
+		test(stream -> stream.collect(A_SUPPLIER, AN_OBJ_LONG_CONSUMER, A_BICONSUMER), 7L);
+		test(LongStream::count, 3L);
+		test(LongStream::findAny, OptionalLong.of(3));
+		test(LongStream::findFirst, OptionalLong.of(3));
+		test(stream -> stream.forEach(AN_LONG_CONSUMER));
+		test(stream -> stream.forEachOrdered(AN_LONG_CONSUMER));
+		test(stream -> stream.max(), OptionalLong.of(3));
+		test(stream -> stream.min(), OptionalLong.of(3));
+		test(stream -> stream.noneMatch(AN_LONG_PREDICATE), true);
+		test(stream -> stream.reduce(AN_LONG_BINARY_OPERATOR), OptionalLong.of(3));
+		test(stream -> stream.reduce(1, AN_LONG_BINARY_OPERATOR), 3L);
+		test(LongStream::toArray, new long[1]);
+		test(LongStream::sum, 1L);
+		test(LongStream::average, OptionalDouble.of(3d));
+		test(LongStream::summaryStatistics, new LongSummaryStatistics());
+	}
+
+	private static <T> void test(Consumer<LongStream> consumer) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return null;
+			}
+
+			@Override
+			public T apply(LongStream stream) {
+				consumer.accept(stream);
+				return null;
+			}
+		});
+	}
+
+	private static <T> void test(Function<LongStream, T> function, T result) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return result;
+			}
+
+			@Override
+			public T apply(LongStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	private static <T extends BaseStream> void test(Class<? extends T> type, Function<LongStream, T> function) {
+		INTERMEDIATE_OPERATIONS.add(new LongermediateOperation<T>() {
+			@Override
+			public Class<? extends T> type() {
+				return type;
+			}
+
+			@Override
+			public T apply(LongStream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	private LongStream delegate;
+	private LongStream inTest;
+
+	@Before
+	public void setUp() {
+		delegate = mock(LongStream.class);
+		inTest = AutoClosingLongStream.from(delegate);
+	}
+
+	@Theory
+	public void testIntermediateOperationReturnsNewAutoClosingStream(@FromDataPoints("intermediateOperations") LongermediateOperation intermediateOperation) {
+		BaseStream newDelegate = (BaseStream) mock(intermediateOperation.type());
+		when(intermediateOperation.apply(delegate)).thenReturn(newDelegate);
+
+		BaseStream result = intermediateOperation.apply(inTest);
+
+		assertThat(result, isAutoClosing());
+		verifyDelegate(result, newDelegate);
+	}
+
+	@Theory
+	public void testTerminalOperationDelegatesToAndClosesDelegate(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		Object expectedResult = terminalOperation.result();
+		if (expectedResult != null) {
+			when(terminalOperation.apply(delegate)).thenReturn(expectedResult);
+		}
+
+		Object result = terminalOperation.apply(inTest);
+
+		InOrder inOrder = inOrder(delegate);
+		assertThat(result, is(expectedResult));
+		inOrder.verify(delegate).close();
+	}
+
+	@Theory
+	public void testTerminalOperationClosesDelegateEvenOnException(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		RuntimeException exception = new RuntimeException();
+		terminalOperation.apply(doThrow(exception).when(delegate));
+
+		thrown.expect(is(exception));
+
+		try {
+			terminalOperation.apply(inTest);
+		} finally {
+			verify(delegate).close();
+		}
+	}
+
+	private Matcher<BaseStream> isAutoClosing() {
+		return is(anyOf(instanceOf(AutoClosingStream.class), instanceOf(AutoClosingDoubleStream.class), instanceOf(AutoClosingIntStream.class), instanceOf(AutoClosingLongStream.class)));
+	}
+
+	private void verifyDelegate(BaseStream result, BaseStream newDelegate) {
+		result.close();
+		verify(newDelegate).close();
+	}
+
+	private interface TerminalOperation<T> {
+
+		T result();
+
+		T apply(LongStream stream);
+
+	}
+
+	private interface LongermediateOperation<T extends BaseStream> {
+
+		Class<? extends T> type();
+
+		T apply(LongStream stream);
+
+	}
+
+}

+ 187 - 16
main/commons/src/test/java/org/cryptomator/common/streams/AutoClosingStreamTest.java

@@ -1,24 +1,160 @@
 package org.cryptomator.common.streams;
 
+import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+import java.util.stream.BaseStream;
+import java.util.stream.Collector;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
 import java.util.stream.Stream;
 
-import org.cryptomator.common.streams.AutoClosingStream;
+import org.hamcrest.Matcher;
 import org.junit.Before;
-import org.junit.Test;
+import org.junit.Rule;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.FromDataPoints;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 
-@SuppressWarnings("unchecked")
+@SuppressWarnings({"unchecked", "rawtypes"})
+@RunWith(Theories.class)
 public class AutoClosingStreamTest {
 
+	private static final Predicate A_PREDICATE = any -> true;
+	private static final Function A_FUNCTION = any -> null;
+	private static final ToDoubleFunction A_TO_DOUBLE_FUNCTION = any -> 0d;
+	private static final ToIntFunction A_TO_INT_FUNCTION = any -> 1;
+	private static final ToLongFunction A_TO_LONG_FUNCTION = any -> 1L;
+	private static final Consumer A_CONSUMER = any -> {
+	};
+	private static final Comparator A_COMPARATOR = (left, right) -> 0;
+	private static final Collector A_COLLECTOR = mock(Collector.class);
+	private static final BinaryOperator A_BINARY_OPERATOR = (left, right) -> null;
+	private static final Object AN_OBJECT = new Object();
+	private static final IntFunction AN_INT_FUNCTION = i -> null;
+	private static final BiConsumer A_BICONSUMER = (a, b) -> {
+	};
+	private static final Supplier A_SUPPLIER = () -> null;
+
+	@DataPoints("intermediateOperations")
+	public static final List<IntermediateOperation<?>> INTERMEDIATE_OPERATIONS = new ArrayList<>();
+
+	@DataPoints("terminalOperations")
+	public static final List<TerminalOperation<?>> TERMINAL_OPERATIONS = new ArrayList<>();
+
+	static {
+		// define intermediate operations
+		test(Stream.class, Stream::distinct);
+		test(Stream.class, stream -> stream.filter(A_PREDICATE));
+		test(Stream.class, stream -> stream.flatMap(A_FUNCTION));
+		test(DoubleStream.class, stream -> stream.flatMapToDouble(A_FUNCTION));
+		test(IntStream.class, stream -> stream.flatMapToInt(A_FUNCTION));
+		test(LongStream.class, stream -> stream.flatMapToLong(A_FUNCTION));
+		test(Stream.class, stream -> stream.limit(5));
+		test(Stream.class, stream -> stream.map(A_FUNCTION));
+		test(DoubleStream.class, stream -> stream.mapToDouble(A_TO_DOUBLE_FUNCTION));
+		test(IntStream.class, stream -> stream.mapToInt(A_TO_INT_FUNCTION));
+		test(LongStream.class, stream -> stream.mapToLong(A_TO_LONG_FUNCTION));
+		test(Stream.class, Stream::parallel);
+		test(Stream.class, stream -> stream.peek(A_CONSUMER));
+		test(Stream.class, Stream::sequential);
+		test(Stream.class, stream -> stream.skip(5));
+		test(Stream.class, Stream::sorted);
+		test(Stream.class, stream -> stream.sorted(A_COMPARATOR));
+		test(Stream.class, Stream::unordered);
+
+		// define terminal operations
+		test(stream -> stream.allMatch(A_PREDICATE), true);
+		test(stream -> stream.anyMatch(A_PREDICATE), true);
+		test(stream -> stream.collect(A_COLLECTOR), new Object());
+		test(stream -> stream.collect(A_SUPPLIER, A_BICONSUMER, A_BICONSUMER), new Object());
+		test(Stream::count, 3L);
+		test(Stream::findAny, Optional.of(new Object()));
+		test(Stream::findFirst, Optional.of(new Object()));
+		test(stream -> stream.forEach(A_CONSUMER));
+		test(stream -> stream.forEachOrdered(A_CONSUMER));
+		test(stream -> stream.max(A_COMPARATOR), Optional.of(new Object()));
+		test(stream -> stream.min(A_COMPARATOR), Optional.of(new Object()));
+		test(stream -> stream.noneMatch(A_PREDICATE), true);
+		test(stream -> stream.reduce(A_BINARY_OPERATOR), Optional.of(new Object()));
+		test(stream -> stream.reduce(AN_OBJECT, A_BINARY_OPERATOR), Optional.of(new Object()));
+		test(stream -> stream.reduce(AN_OBJECT, A_BINARY_OPERATOR, A_BINARY_OPERATOR), Optional.of(new Object()));
+		test(Stream::toArray, new Object[1]);
+		test(stream -> stream.toArray(AN_INT_FUNCTION), new Object[1]);
+	}
+
+	private static <T> void test(Consumer<Stream> consumer) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return null;
+			}
+
+			@Override
+			public T apply(Stream stream) {
+				consumer.accept(stream);
+				return null;
+			}
+		});
+	}
+
+	private static <T> void test(Function<Stream, T> function, T result) {
+		TERMINAL_OPERATIONS.add(new TerminalOperation<T>() {
+			@Override
+			public T result() {
+				return result;
+			}
+
+			@Override
+			public T apply(Stream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	private static <T extends BaseStream> void test(Class<? extends T> type, Function<Stream, T> function) {
+		INTERMEDIATE_OPERATIONS.add(new IntermediateOperation<T>() {
+			@Override
+			public Class<? extends T> type() {
+				return type;
+			}
+
+			@Override
+			public T apply(Stream stream) {
+				return function.apply(stream);
+			}
+		});
+	}
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
 	private Stream<Object> delegate;
 	private Stream<Object> inTest;
 
@@ -28,33 +164,68 @@ public class AutoClosingStreamTest {
 		inTest = AutoClosingStream.from(delegate);
 	}
 
-	@Test
-	public void testSequentialReturnsNewAutoClosingStream() {
-		Stream<Object> newDelegate = mock(Stream.class);
-		when(delegate.sequential()).thenReturn(newDelegate);
+	@Theory
+	public void testIntermediateOperationReturnsNewAutoClosingStream(@FromDataPoints("intermediateOperations") IntermediateOperation intermediateOperation) {
+		BaseStream newDelegate = (BaseStream) mock(intermediateOperation.type());
+		when(intermediateOperation.apply(delegate)).thenReturn(newDelegate);
 
-		Stream<Object> result = inTest.sequential();
+		BaseStream result = intermediateOperation.apply(inTest);
 
-		assertThat(result, is(instanceOf(AutoClosingStream.class)));
+		assertThat(result, isAutoClosing());
 		verifyDelegate(result, newDelegate);
 	}
 
-	@Test
-	public void testForEachDelegatesToAndClosesDelegate() {
-		Consumer<Object> consumer = mock(Consumer.class);
+	@Theory
+	public void testTerminalOperationDelegatesToAndClosesDelegate(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		Object expectedResult = terminalOperation.result();
+		if (expectedResult != null) {
+			when(terminalOperation.apply(delegate)).thenReturn(expectedResult);
+		}
 
-		inTest.forEach(consumer);
+		Object result = terminalOperation.apply(inTest);
 
 		InOrder inOrder = inOrder(delegate);
-		inOrder.verify(delegate).forEach(consumer);
+		assertThat(result, is(expectedResult));
 		inOrder.verify(delegate).close();
 	}
 
-	private void verifyDelegate(Stream<Object> result, Stream<Object> newDelegate) {
+	@Theory
+	public void testTerminalOperationClosesDelegateEvenOnException(@FromDataPoints("terminalOperations") TerminalOperation terminalOperation) {
+		RuntimeException exception = new RuntimeException();
+		terminalOperation.apply(doThrow(exception).when(delegate));
+
+		thrown.expect(is(exception));
+
+		try {
+			terminalOperation.apply(inTest);
+		} finally {
+			verify(delegate).close();
+		}
+	}
+
+	private Matcher<BaseStream> isAutoClosing() {
+		return is(anyOf(instanceOf(AutoClosingStream.class), instanceOf(AutoClosingDoubleStream.class), instanceOf(AutoClosingIntStream.class), instanceOf(AutoClosingLongStream.class)));
+	}
+
+	private void verifyDelegate(BaseStream result, BaseStream newDelegate) {
 		result.close();
 		verify(newDelegate).close();
 	}
 
-	// TODO Markus Kreusch test additional methods
+	private interface TerminalOperation<T> {
+
+		T result();
+
+		T apply(Stream stream);
+
+	}
+
+	private interface IntermediateOperation<T extends BaseStream> {
+
+		Class<? extends T> type();
+
+		T apply(Stream stream);
+
+	}
 
 }