blob: 08c4c897398ae6e611a991242c7e67a7cc26bdd5 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* 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.gwt.emultest.java8.util.stream;
import static java.util.Arrays.asList;
import com.google.gwt.emultest.java.util.EmulTestBase;
import com.google.gwt.testing.TestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Spliterator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
/**
* Tests {@link Stream}.
*/
public class StreamTest extends EmulTestBase {
public void testEmptyStream() {
Stream<Object> empty = Stream.empty();
assertEquals(0, empty.count());
try {
empty.count();
fail("second terminal operation should have thrown IllegalStateEx");
} catch (IllegalStateException expected) {
// expected
}
assertEquals(0, Stream.empty().limit(2).collect(Collectors.toList()).size());
assertEquals(0, Stream.empty().count());
assertEquals(0, Stream.empty().limit(2).count());
assertFalse(Stream.empty().findFirst().isPresent());
assertFalse(Stream.empty().findAny().isPresent());
assertFalse(Stream.<String>empty().max(Comparator.naturalOrder()).isPresent());
assertFalse(Stream.<String>empty().min(Comparator.naturalOrder()).isPresent());
assertTrue(Stream.empty().allMatch(item -> true));
assertFalse(Stream.empty().anyMatch(item -> true));
assertTrue(Stream.empty().noneMatch(item -> false));
assertFalse(Stream.empty().iterator().hasNext());
assertFalse(Stream.empty().spliterator().tryAdvance(a -> fail("should not advance")));
Stream.empty().spliterator().forEachRemaining(a -> fail("should not advance"));
assertEquals(new Object[0], Stream.empty().toArray());
assertEquals(new Object[0], Stream.empty().toArray(Object[]::new));
}
public void testStreamOfOne() {
Supplier<Stream<String>> one = () -> Stream.of("");
assertEquals(Collections.singletonList(""), one.get().collect(Collectors.toList()));
assertEquals(1L, one.get().count());
assertEquals("", one.get().findFirst().get());
assertEquals("", one.get().findAny().get());
}
public void testBuilder() {
Supplier<Stream<String>> s = () -> Stream.<String>builder().add("1").add("3").add("2").build();
Optional<String> max = s.get().filter(str -> !str.equals("3")).max(Comparator.naturalOrder());
assertTrue(max.isPresent());
assertEquals("2", max.get());
max = s.get().max(Comparator.reverseOrder());
assertTrue(max.isPresent());
assertEquals("1", max.get());
Stream.Builder<Object> builder = Stream.builder();
Stream<Object> built = builder.build();
assertEquals(0, built.count());
try {
builder.build();
fail("build() after build() should fail");
} catch (IllegalStateException expected) {
// expected
}
try {
builder.add("asdf");
fail("add() after build() should fail");
} catch (IllegalStateException expected) {
// expected
}
}
public void testConcat() {
Supplier<Stream<String>> adbc = () -> Stream.concat(Stream.of("a", "d"), Stream.of("b", "c"));
assertEquals(new String[] {"a", "d", "b", "c"}, adbc.get().toArray(String[]::new));
assertEquals(new String[] {"a", "b", "c", "d"}, adbc.get().sorted().toArray(String[]::new));
List<String> closed = new ArrayList<>();
Stream<String> first = Stream.of("first").onClose(() -> closed.add("first"));
Stream<String> second = Stream.of("second").onClose(() -> closed.add("second"));
Stream<String> concat = Stream.concat(first, second);
// read everything, make sure we saw it all and didn't close automatically
String collectedAll = concat.collect(Collectors.joining());
assertEquals("firstsecond", collectedAll);
assertEquals(0, closed.size());
concat.close();
assertEquals(Arrays.asList("first", "second"), closed);
}
public void testIterate() {
assertEquals(
new Integer[] {10, 11, 12, 13, 14},
Stream.iterate(0, i -> i + 1).skip(10).limit(5).toArray(Integer[]::new));
}
public void testGenerate() {
// infinite, but if you limit it is already too short to skip much
assertEquals(
new Integer[] {},
Stream.generate(makeGenerator()).limit(4).skip(5).toArray(Integer[]::new));
assertEquals(
new Integer[] {10, 11, 12, 13, 14},
Stream.generate(makeGenerator()).skip(10).limit(5).toArray(Integer[]::new));
}
private Supplier<Integer> makeGenerator() {
return new Supplier<Integer>() {
int next = 0;
@Override
public Integer get() {
return next++;
}
};
}
public void testSpliterator() {
final String[] values = new String[] {"a", "b", "c"};
Spliterator<String> spliterator = Stream.of(values).spliterator();
assertEquals(3, spliterator.estimateSize());
assertEquals(3, spliterator.getExactSizeIfKnown());
List<String> actualValues = new ArrayList<>();
while (spliterator.tryAdvance(actualValues::add)) {
// work is all done in the condition
}
assertEquals(asList(values), actualValues);
}
public void testIterator() {
final String[] values = new String[] {"a", "b", "c"};
List<String> actualValues = new ArrayList<String>();
Iterator<String> iterator = Stream.of(values).iterator();
while (iterator.hasNext()) {
actualValues.add(iterator.next());
}
assertEquals(asList(values), actualValues);
}
public void testForEach() {
final String[] values = new String[] {"a", "b", "c"};
List<String> actualValues = new ArrayList<>();
Stream.of(values).forEach(actualValues::add);
assertEquals(asList(values), actualValues);
}
// toArray
public void testToArray() {
assertEquals(new Object[] {"a", "b"}, asList("a", "b").stream().toArray());
assertEquals(new String[] {"a", "b"}, asList("a", "b").stream().toArray(String[]::new));
}
// reduce
public void testReduce() {
String reduced = Stream.of("a", "b", "c").reduce("", String::concat);
assertEquals("abc", reduced);
reduced = Stream.<String>of().reduce("initial", String::concat);
assertEquals("initial", reduced);
Optional<String> maybe = Stream.of("a", "b", "c").reduce(String::concat);
assertTrue(maybe.isPresent());
assertEquals("abc", maybe.get());
maybe = Stream.<String>of().reduce(String::concat);
assertFalse(maybe.isPresent());
reduced = Stream.of("a", "b", "c").reduce("", String::concat, String::concat);
assertEquals("abc", reduced);
}
public void testCollect() {
final String[] values = new String[] {"a", "b", "c"};
String collectedString =
Stream.of(values)
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
assertEquals("abc", collectedString);
List<String> collectedList = Stream.of(values).collect(Collectors.toList());
assertEquals(asList(values), collectedList);
}
public void testFilter() {
// unconsumed stream never runs filter
boolean[] data = {false};
Stream.of(1, 2, 3).filter(i -> data[0] |= true);
assertFalse(data[0]);
// Nothing's *defined* to care about the Spliterator characteristics, but the implementation
// can't actually know the size before executing, so we check the characteristics explicitly.
assertFalse(
Stream.of("a", "b", "c", "d", "c")
.filter(a -> a.equals("a"))
.spliterator()
.hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED));
// one result
assertEquals(
Collections.singletonList("a"),
Stream.of("a", "b", "c", "d", "c").filter(a -> a.equals("a")).collect(Collectors.toList()));
// zero results
assertEquals(
Collections.emptyList(),
Stream.of("a", "b", "c", "d", "c").filter(a -> false).collect(Collectors.toList()));
// two results
assertEquals(
asList("c3", "c5"),
Stream.of("a1", "b2", "c3", "d4", "c5")
.filter(a -> a.startsWith("c"))
.collect(Collectors.toList()));
// all
assertEquals(
asList("a", "b", "c", "d", "c"),
Stream.of("a", "b", "c", "d", "c").filter(a -> true).collect(Collectors.toList()));
}
public void testMap() {
// unconsumed stream never runs map
boolean[] data = {false};
Stream.of(1, 2, 3).map(i -> data[0] |= true);
assertFalse(data[0]);
assertEquals(
asList("#1", "#2", "#3"),
Stream.of(1, 2, 3).map(i -> "#" + i).collect(Collectors.toList()));
}
public void testPeek() {
// unconsumed stream never peeks
boolean[] data = {false};
Stream.of(1, 2, 3).peek(i -> data[0] |= true);
assertFalse(data[0]);
// make sure we saw it all in order
List<String> items = asList("a", "b", "c");
List<String> peeked = new ArrayList<>();
items
.stream()
.peek(peeked::add)
.forEach(
item -> {
// deliberately do nothing, just run
});
assertEquals(items, peeked);
}
// same impl, no parallel in browser
public void testFindFirstOrAny() {
Optional<String> any = Stream.of("a", "b").findAny();
assertTrue(any.isPresent());
assertEquals("a", any.get());
}
public void testAnyMatch() {
// all
assertTrue(Stream.of("a", "b").anyMatch(s -> true));
// some
assertTrue(Stream.of("a", "b").anyMatch(s -> s.equals("a")));
// none
assertFalse(Stream.of("a", "b").anyMatch(s -> false));
// With null values.
// all
assertTrue(Stream.of(null, "a", "b").anyMatch(s -> true));
// some
assertTrue(Stream.of(null, "a", "b").anyMatch(s -> s == null));
// none
assertFalse(Stream.of(null, "a", "b").anyMatch(s -> false));
}
public void testAllMatch() {
// all
assertTrue(Stream.of("a", "b").allMatch(s -> true));
// some
assertFalse(Stream.of("a", "b").allMatch(s -> s.equals("a")));
// none
assertFalse(Stream.of("a", "b").allMatch(s -> false));
// With null values.
// all
assertTrue(Stream.of(null, "a", "b").allMatch(s -> true));
// some
assertFalse(Stream.of(null, "a", "b").allMatch(s -> s != null));
// none
assertFalse(Stream.of(null, "a", "b").allMatch(s -> false));
}
public void testNoneMatch() {
// all
assertFalse(Stream.of("a", "b").noneMatch(s -> true));
// some
assertFalse(Stream.of("a", "b").noneMatch(s -> s.equals("a")));
// none
assertTrue(Stream.of("a", "b").noneMatch(s -> false));
// With null values.
// all
assertFalse(Stream.of(null, "a", "b").noneMatch(s -> true));
// some
assertFalse(Stream.of(null, "a", "b").noneMatch(s -> s == null));
// none
assertTrue(Stream.of(null, "a", "b").noneMatch(s -> false));
}
public void testFlatMap() {
assertEquals(0, Stream.<Stream<String>>empty().flatMap(Function.identity()).count());
assertEquals(0, Stream.of(Stream.<String>empty()).flatMap(Function.identity()).count());
assertEquals(0, Stream.of(Stream.of()).flatMap(Function.identity()).count());
assertEquals(1, Stream.of(Stream.of("")).flatMap(Function.identity()).count());
Stream<Stream<String>> strings = Stream.of(Stream.of("a", "b"), Stream.empty(), Stream.of("c"));
assertEquals(
asList("a", "b", "c"), strings.flatMap(Function.identity()).collect(Collectors.toList()));
}
public void testMapToPrimitives() {
Supplier<Stream<String>> s = () -> Stream.of("1", "2", "10");
assertEquals(new int[] {1, 2, 10}, s.get().mapToInt(Integer::parseInt).toArray());
assertEquals(new long[] {1, 2, 10}, s.get().mapToLong(Long::parseLong).toArray());
assertEquals(new double[] {1, 2, 10}, s.get().mapToDouble(Double::parseDouble).toArray());
}
public void testFlatMapToPrimitives() {
assertEquals(0, Stream.<IntStream>empty().flatMapToInt(Function.identity()).count());
assertEquals(0, Stream.of(IntStream.empty()).flatMapToInt(Function.identity()).count());
assertEquals(0, Stream.of(IntStream.of()).flatMapToInt(Function.identity()).count());
assertEquals(1, Stream.of(IntStream.of(0)).flatMapToInt(Function.identity()).count());
Stream<IntStream> intStreams =
Stream.of(IntStream.of(1, 2), IntStream.empty(), IntStream.of(5));
assertEquals(new int[] {1, 2, 5}, intStreams.flatMapToInt(Function.identity()).toArray());
Stream<LongStream> longStreams =
Stream.of(LongStream.of(1, 2), LongStream.empty(), LongStream.of(5));
assertEquals(new long[] {1, 2, 5}, longStreams.flatMapToLong(Function.identity()).toArray());
Stream<DoubleStream> doubleStreams =
Stream.of(DoubleStream.of(1, 2), DoubleStream.empty(), DoubleStream.of(5));
assertEquals(
new double[] {1, 2, 5}, doubleStreams.flatMapToDouble(Function.identity()).toArray());
}
public void testDistinct() {
List<String> distinct =
asList("a", "b", "c", "b").stream().distinct().collect(Collectors.toList());
assertEquals(3, distinct.size());
assertTrue(distinct.contains("a"));
assertTrue(distinct.contains("b"));
assertTrue(distinct.contains("c"));
}
public void testSorted() {
List<String> sorted = asList("c", "a", "b").stream().sorted().collect(Collectors.toList());
List<String> reversed =
asList("c", "a", "b")
.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
assertEquals(asList("a", "b", "c"), sorted);
assertEquals(asList("c", "b", "a"), reversed);
}
public void testMinMax() {
Supplier<Stream<String>> stream = () -> Stream.of("b", "c", "d", "a");
assertEquals("a", stream.get().min(Comparator.naturalOrder()).orElse(null));
assertEquals("d", stream.get().min(Comparator.reverseOrder()).orElse(null));
assertEquals("a", stream.get().max(Comparator.reverseOrder()).orElse(null));
assertEquals("d", stream.get().max(Comparator.naturalOrder()).orElse(null));
assertFalse(stream.get().filter(a -> false).max(Comparator.naturalOrder()).isPresent());
assertFalse(stream.get().filter(a -> false).min(Comparator.naturalOrder()).isPresent());
}
public void testCountLimitSkip() {
Supplier<Stream<String>> stream = () -> asList("a", "b", "c", "d").stream();
assertEquals(4, stream.get().count());
assertEquals(4, stream.get().limit(4).count());
assertEquals(4, stream.get().limit(5).count());
assertEquals(3, stream.get().limit(3).count());
assertEquals(3, stream.get().skip(1).limit(3).count());
assertEquals(2, stream.get().limit(3).skip(1).count());
assertEquals(1, stream.get().skip(3).count());
assertEquals(asList("c", "d"), stream.get().skip(2).limit(3).collect(Collectors.toList()));
assertEquals(
Collections.singletonList("c"), stream.get().skip(2).limit(1).collect(Collectors.toList()));
assertEquals(Collections.singletonList("d"), stream.get().skip(3).collect(Collectors.toList()));
assertEquals(Collections.emptyList(), stream.get().skip(5).collect(Collectors.toList()));
assertEquals(asList("a", "b"), stream.get().limit(2).collect(Collectors.toList()));
assertEquals(
Collections.singletonList("b"), stream.get().limit(2).skip(1).collect(Collectors.toList()));
}
// This frustrating test was written first on the JVM stream to discover the basic behavior before
// trying to implement it in GWT. As far as I can tell, none of this is clearly described in
// javadoc. Also note that it is *not* required to use the returned stream from calling onClose
public void testCloseQuirks() {
// all subclasses use the same close()/onClose(...) impl, just test once with Stream.empty()
Stream<Object> s = Stream.of(1);
s.close();
// allow multiple close
s.close();
// Add a handler, close, and attempt to re-close - handler only runs the one time
int[] calledCount = {0};
s = Stream.empty();
s.onClose(() -> calledCount[0]++);
// shouldn't have been called yet
assertEquals(0, calledCount[0]);
s.close();
// called once
assertEquals(1, calledCount[0]);
s.close();
// not called again on subsequent closes
assertEquals(1, calledCount[0]);
if (TestUtils.getJdkVersion() >= 11) {
try {
s = s.onClose(() -> calledCount[0]++);
fail();
} catch (IllegalStateException expected) {
}
return;
}
// Add a handler after close, and re-close, the handler will only go off after the _second_
// close
calledCount[0] = 0;
s = Stream.of(1);
s.close();
s = s.onClose(() -> calledCount[0]++);
// shouldn't have been called yet
assertEquals(0, calledCount[0]);
s.close();
// frustratingly, the JVM apparently permits each handler when added to let the stream be closed
// _again_
assertEquals(1, calledCount[0]);
// Adding yet another runnable and closing again demonstrates this - only the new one is run,
// the old ones are not
s = s.onClose(() -> calledCount[0]++);
s.close();
assertEquals(2, calledCount[0]);
// Add two handlers, ensure both are called, and neither called the second time the stream is
// closed
calledCount[0] = 0;
s = Stream.empty();
s = s.onClose(() -> calledCount[0]++);
s = s.onClose(() -> calledCount[0]++);
s.close();
assertEquals(2, calledCount[0]);
s.close();
assertEquals(2, calledCount[0]);
}
public void testClose() {
// terminate stream before closing, confirm that handler is called
Stream<Object> s = Stream.of("a", "b", "c");
int[] calledCount = {0};
s = s.onClose(() -> calledCount[0]++);
long count = s.count();
assertEquals(3, count);
assertEquals(0, calledCount[0]);
s.close();
assertEquals(1, calledCount[0]);
// terminate stream after closing, confirm that handler is called, and terminating fails
s = Stream.of("a", "b", "c");
calledCount[0] = 0;
s = s.onClose(() -> calledCount[0]++);
s.close();
assertEquals(1, calledCount[0]);
try {
s.count();
fail("Expected IllegalStateException");
} catch (IllegalStateException expected) {
// expected
}
assertEquals(1, calledCount[0]);
}
public void testCloseException() {
// Try a single exception, confirm we catch it
Stream<Object> s = Stream.of(1, 2, 3);
RuntimeException a = new RuntimeException("a");
s.onClose(
() -> {
throw a;
});
try {
s.close();
fail("RuntimeException expected");
} catch (RuntimeException expected) {
assertSame(a, expected);
assertEquals(0, expected.getSuppressed().length);
}
// Throw an exception in two of the three handlers, confirm both arrive and the third was called
// correctly
s = Stream.of(1, 2, 3);
RuntimeException a2 = new RuntimeException("a");
IllegalStateException b = new IllegalStateException("b");
int[] calledCount = {0};
s.onClose(
() -> {
throw a2;
})
.onClose(
() -> {
throw b;
})
.onClose(
() -> {
throw a2;
})
.onClose(
() -> {
throw b;
})
.onClose(() -> calledCount[0]++);
try {
s.close();
fail("RuntimeException expected");
} catch (RuntimeException expected) {
assertSame(a2, expected);
assertEquals(new Throwable[] {b, b}, expected.getSuppressed());
}
assertEquals(1, calledCount[0]);
// Throw the same exception instance twice, ensure it only arrives once
s = Stream.of(1, 2, 3);
RuntimeException t = new RuntimeException("a");
s.onClose(
() -> {
throw t;
})
.onClose(
() -> {
throw t;
});
try {
s.close();
fail("RuntimeException expected");
} catch (RuntimeException expected) {
assertSame(t, expected);
assertEquals(0, expected.getSuppressed().length);
}
}
}