1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.openjdk.tests.java.util.stream;
24
25import org.testng.annotations.Test;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.HashMap;
30import java.util.LinkedHashSet;
31import java.util.List;
32import java.util.Map;
33import java.util.Set;
34import java.util.concurrent.atomic.AtomicBoolean;
35import java.util.function.Function;
36import java.util.function.Predicate;
37import java.util.stream.DefaultMethodStreams;
38import java.util.stream.DoubleStream;
39import java.util.stream.IntStream;
40import java.util.stream.LambdaTestHelpers;
41import java.util.stream.LongStream;
42import java.util.stream.OpTestCase;
43import java.util.stream.Stream;
44import java.util.stream.StreamTestDataProvider;
45import java.util.stream.TestData;
46
47/*
48 * @test
49 * @bug 8071597
50 */
51@Test
52public class WhileOpTest extends OpTestCase {
53
54    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class,
55          groups = { "serialization-hostile" })
56    public void testTakeWhileOps(String name, TestData.OfRef<Integer> data) {
57        for (int size : sizes(data.size())) {
58            setContext("takeWhile", size);
59
60            testWhileMulti(data,
61                           whileResultAsserter(data, WhileOp.Take, e -> e < size),
62                           s -> s.takeWhile(e -> e < size),
63                           s -> s.takeWhile(e -> e < size),
64                           s -> s.takeWhile(e -> e < size),
65                           s -> s.takeWhile(e -> e < size));
66
67
68            testWhileMulti(data,
69                           whileResultAsserter(data, WhileOp.Take, e -> e < size / 2),
70                           s -> s.takeWhile(e -> e < size).takeWhile(e -> e < size / 2),
71                           s -> s.takeWhile(e -> e < size).takeWhile(e -> e < size / 2),
72                           s -> s.takeWhile(e -> e < size).takeWhile(e -> e < size / 2),
73                           s -> s.takeWhile(e -> e < size).takeWhile(e -> e < size / 2));
74        }
75    }
76
77    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class,
78          groups = { "serialization-hostile" })
79    public void testDropWhileOps(String name, TestData.OfRef<Integer> data) {
80        for (int size : sizes(data.size())) {
81            setContext("dropWhile", size);
82
83            testWhileMulti(data,
84                           whileResultAsserter(data, WhileOp.Drop, e -> e < size),
85                           s -> s.dropWhile(e -> e < size),
86                           s -> s.dropWhile(e -> e < size),
87                           s -> s.dropWhile(e -> e < size),
88                           s -> s.dropWhile(e -> e < size));
89
90            testWhileMulti(data,
91                           whileResultAsserter(data, WhileOp.Drop, e -> e < size),
92                           s -> s.dropWhile(e -> e < size / 2).dropWhile(e -> e < size),
93                           s -> s.dropWhile(e -> e < size / 2).dropWhile(e -> e < size),
94                           s -> s.dropWhile(e -> e < size / 2).dropWhile(e -> e < size),
95                           s -> s.dropWhile(e -> e < size / 2).dropWhile(e -> e < size));
96        }
97    }
98
99    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class,
100          groups = { "serialization-hostile" })
101    public void testDropTakeWhileOps(String name, TestData.OfRef<Integer> data) {
102        for (int size : sizes(data.size())) {
103            setContext("dropWhile", size);
104
105            testWhileMulti(data,
106                           whileResultAsserter(data, WhileOp.Undefined, null),
107                           s -> s.dropWhile(e -> e < size / 2).takeWhile(e -> e < size),
108                           s -> s.dropWhile(e -> e < size / 2).takeWhile(e -> e < size),
109                           s -> s.dropWhile(e -> e < size / 2).takeWhile(e -> e < size),
110                           s -> s.dropWhile(e -> e < size / 2).takeWhile(e -> e < size));
111        }
112    }
113
114    /**
115     * While operation type to be asserted on
116     */
117    enum WhileOp {
118        /**
119         * The takeWhile operation
120         */
121        Take,
122        /**
123         * The dropWhile operation
124         */
125        Drop,
126        /**
127         * The operation(s) are undefined
128         */
129        Undefined
130    }
131
132    /**
133     * Create a result asserter for takeWhile or dropWhile operations.
134     * <p>
135     * If the stream pipeline consists of the takeWhile operation
136     * ({@link WhileOp#Take}) or the dropWhile operation ({@link WhileOp#Drop})
137     * then specific assertions can be made on the actual result based on the
138     * input elements, {@code inputData}, and whether those elements match the
139     * predicate, {@code p}, of the operation.
140     * <p>
141     * If the input elements have an encounter order then the actual result
142     * is asserted against the result of operating sequentially on input
143     * elements given the predicate and in accordance with the operation
144     * semantics. (The actual result whether produced sequentially or in
145     * parallel should the same.)
146     * <p>
147     * If the input elements have no encounter order then an actual result
148     * is, for practical purposes, considered non-deterministic.
149     * Consider an input list of lists that contains all possible permutations
150     * of the input elements, and a output list of lists that is the result of
151     * applying the pipeline with the operation sequentially to each input
152     * list.
153     * Any list in the output lists is a valid result. It's not practical to
154     * test in such a manner.
155     * For a takeWhile operation the following assertions can be made if
156     * only some of the input elements match the predicate (i.e. taking will
157     * short-circuit the pipeline):
158     * <ol>
159     * <li>The set of output elements is a subset of the set of matching
160     * input elements</li>
161     * <li>The set of output elements and the set of non-matching input
162     * element are disjoint</li>
163     * </ol>
164     * For a dropWhile operation the following assertions can be made:
165     * <ol>
166     * <li>The set of non-matching input elements is a subset of the set of
167     * output elements</li>
168     * <li>The set of matching output elements is a subset of the set of
169     * matching input elements</li>
170     * </ol>
171     *
172     * @param inputData the elements input into the stream pipeline
173     * @param op the operation of the stream pipeline, one of takeWhile,
174     * dropWhile, or an undefined set of operations (possibly including
175     * two or more takeWhile and/or dropWhile operations, or because
176     * the predicate is not stateless).
177     * @param p the stateless predicate applied to the operation, ignored if
178     * the
179     * operation is {@link WhileOp#Undefined}.
180     * @param <T> the type of elements
181     * @return a result asserter
182     */
183    private <T> ResultAsserter<Iterable<T>> whileResultAsserter(Iterable<T> inputData,
184                                                                WhileOp op,
185                                                                Predicate<? super T> p) {
186        return (act, exp, ord, par) -> {
187            if (par & !ord) {
188                List<T> input = new ArrayList<>();
189                inputData.forEach(input::add);
190
191                List<T> output = new ArrayList<>();
192                act.forEach(output::add);
193
194                if (op == WhileOp.Take) {
195                    List<T> matchingInput = new ArrayList<>();
196                    List<T> nonMatchingInput = new ArrayList<>();
197                    input.forEach(t -> {
198                        if (p.test(t))
199                            matchingInput.add(t);
200                        else
201                            nonMatchingInput.add(t);
202                    });
203
204                    // If some, not all, elements are taken
205                    if (matchingInput.size() < input.size()) {
206                        assertTrue(output.size() <= matchingInput.size(),
207                                   "Output is larger than the matching input");
208
209                        // The output must be a subset of the matching input
210                        assertTrue(matchingInput.containsAll(output),
211                                   "Output is not a subset of the matching input");
212
213                        // The output must not contain any non matching elements
214                        for (T nonMatching : nonMatchingInput) {
215                            assertFalse(output.contains(nonMatching),
216                                        "Output and non-matching input are not disjoint");
217                        }
218                    }
219                }
220                else if (op == WhileOp.Drop) {
221                    List<T> matchingInput = new ArrayList<>();
222                    List<T> nonMatchingInput = new ArrayList<>();
223                    input.forEach(t -> {
224                        if (p.test(t))
225                            matchingInput.add(t);
226                        else
227                            nonMatchingInput.add(t);
228                    });
229
230                    // The non matching input must be a subset of output
231                    assertTrue(output.containsAll(nonMatchingInput),
232                               "Non-matching input is not a subset of the output");
233
234                    // The matching output must be a subset of the matching input
235                    List<T> matchingOutput = new ArrayList<>();
236                    output.forEach(i -> {
237                        if (p.test(i))
238                            matchingOutput.add(i);
239                    });
240                    assertTrue(matchingInput.containsAll(matchingOutput),
241                               "Matching output is not a subset of matching input");
242                }
243
244                // Note: if there is a combination of takeWhile and dropWhile then specific
245                // assertions cannot be performed.
246                // All that can be reliably asserted is the output is a subset of the input
247
248                assertTrue(input.containsAll(output));
249            }
250            else {
251                // For specific operations derive expected result from the input
252                if (op == WhileOp.Take) {
253                    List<T> takeInput = new ArrayList<>();
254                    for (T t : inputData) {
255                        if (p.test(t))
256                            takeInput.add(t);
257                        else
258                            break;
259                    }
260
261                    LambdaTestHelpers.assertContents(act, takeInput);
262                }
263                else if (op == WhileOp.Drop) {
264                    List<T> dropInput = new ArrayList<>();
265                    for (T t : inputData) {
266                        if (dropInput.size() > 0 || !p.test(t))
267                            dropInput.add(t);
268                    }
269
270                    LambdaTestHelpers.assertContents(act, dropInput);
271                }
272
273                LambdaTestHelpers.assertContents(act, exp);
274            }
275        };
276    }
277
278    private Collection<Integer> sizes(int s) {
279        Set<Integer> sizes = new LinkedHashSet<>();
280
281        sizes.add(0);
282        sizes.add(1);
283        sizes.add(s / 4);
284        sizes.add(s / 2);
285        sizes.add(3 * s / 4);
286        sizes.add(Math.max(0, s - 1));
287        sizes.add(s);
288        sizes.add(Integer.MAX_VALUE);
289
290        return sizes;
291    }
292
293    private void testWhileMulti(TestData.OfRef<Integer> data,
294                                ResultAsserter<Iterable<Integer>> ra,
295                                Function<Stream<Integer>, Stream<Integer>> mRef,
296                                Function<IntStream, IntStream> mInt,
297                                Function<LongStream, LongStream> mLong,
298                                Function<DoubleStream, DoubleStream> mDouble) {
299        Map<String, Function<Stream<Integer>, Stream<Integer>>> ms = new HashMap<>();
300        ms.put("Ref", mRef);
301        ms.put("Int", s -> mInt.apply(s.mapToInt(e -> e)).mapToObj(e -> e));
302        ms.put("Long", s -> mLong.apply(s.mapToLong(e -> e)).mapToObj(e -> (int) e));
303        ms.put("Double", s -> mDouble.apply(s.mapToDouble(e -> e)).mapToObj(e -> (int) e));
304        ms.put("Ref using defaults", s -> mRef.apply(DefaultMethodStreams.delegateTo(s)));
305        ms.put("Int using defaults", s -> mInt.apply(DefaultMethodStreams.delegateTo(s.mapToInt(e -> e))).mapToObj(e -> e));
306        ms.put("Long using defaults", s -> mLong.apply(DefaultMethodStreams.delegateTo(s.mapToLong(e -> e))).mapToObj(e -> (int) e));
307        ms.put("Double using defaults", s -> mDouble.apply(DefaultMethodStreams.delegateTo(s.mapToDouble(e -> e))).mapToObj(e -> (int) e));
308
309        testWhileMulti(data, ra, ms);
310    }
311
312    private final void testWhileMulti(TestData.OfRef<Integer> data,
313                                      ResultAsserter<Iterable<Integer>> ra,
314                                      Map<String, Function<Stream<Integer>, Stream<Integer>>> ms) {
315        for (Map.Entry<String, Function<Stream<Integer>, Stream<Integer>>> e : ms.entrySet()) {
316            setContext("shape", e.getKey());
317
318            withData(data)
319                    .stream(e.getValue())
320                    .resultAsserter(ra)
321                    .exercise();
322        }
323    }
324
325    @Test(groups = { "serialization-hostile" })
326    public void testRefDefaultClose() {
327        AtomicBoolean isClosed = new AtomicBoolean();
328        Stream<Integer> s = Stream.of(1, 2, 3).onClose(() -> isClosed.set(true));
329        try (Stream<Integer> ds = DefaultMethodStreams.delegateTo(s).takeWhile(e -> e < 3)) {
330            ds.count();
331        }
332        assertTrue(isClosed.get());
333    }
334
335    @Test(groups = { "serialization-hostile" })
336    public void testIntDefaultClose() {
337        AtomicBoolean isClosed = new AtomicBoolean();
338        IntStream s = IntStream.of(1, 2, 3).onClose(() -> isClosed.set(true));
339        try (IntStream ds = DefaultMethodStreams.delegateTo(s).takeWhile(e -> e < 3)) {
340            ds.count();
341        }
342        assertTrue(isClosed.get());
343    }
344
345    @Test(groups = { "serialization-hostile" })
346    public void testLongDefaultClose() {
347        AtomicBoolean isClosed = new AtomicBoolean();
348        LongStream s = LongStream.of(1, 2, 3).onClose(() -> isClosed.set(true));
349        try (LongStream ds = DefaultMethodStreams.delegateTo(s).takeWhile(e -> e < 3)) {
350            ds.count();
351        }
352        assertTrue(isClosed.get());
353    }
354
355    @Test(groups = { "serialization-hostile" })
356    public void testDoubleDefaultClose() {
357        AtomicBoolean isClosed = new AtomicBoolean();
358        DoubleStream s = DoubleStream.of(1, 2, 3).onClose(() -> isClosed.set(true));
359        try (DoubleStream ds = DefaultMethodStreams.delegateTo(s).takeWhile(e -> e < 3)) {
360            ds.count();
361        }
362        assertTrue(isClosed.get());
363    }
364}
365