2626import static org .junit .Assert .assertTrue ;
2727
2828import java .io .Serializable ;
29+ import java .util .ArrayList ;
30+ import java .util .Arrays ;
31+ import java .util .Collections ;
32+ import java .util .List ;
2933import org .apache .beam .runners .spark .SparkPipelineOptions ;
3034import org .apache .beam .runners .spark .StreamingTest ;
3135import org .apache .beam .runners .spark .io .CreateStream ;
4852import org .apache .beam .sdk .transforms .Flatten ;
4953import org .apache .beam .sdk .transforms .PTransform ;
5054import org .apache .beam .sdk .transforms .ParDo ;
55+ import org .apache .beam .sdk .transforms .SerializableFunction ;
5156import org .apache .beam .sdk .transforms .windowing .FixedWindows ;
57+ import org .apache .beam .sdk .transforms .windowing .IntervalWindow ;
5258import org .apache .beam .sdk .transforms .windowing .Window ;
5359import org .apache .beam .sdk .values .KV ;
5460import org .apache .beam .sdk .values .PBegin ;
@@ -75,13 +81,15 @@ private PTransform<PBegin, PCollection<KV<Integer, Integer>>> createStreamingSou
7581 return CreateStream .of (coder , batchDuration )
7682 .emptyBatch ()
7783 .advanceWatermarkForNextBatch (instant )
78- .nextBatch (TimestampedValue .of (KV .of (1 , 1 ), instant ))
79- .nextBatch (TimestampedValue .of (KV .of (1 , 2 ), instant ))
80- .nextBatch (TimestampedValue .of (KV .of (1 , 3 ), instant ))
84+ .nextBatch (
85+ TimestampedValue .of (KV .of (1 , 1 ), instant ),
86+ TimestampedValue .of (KV .of (1 , 2 ), instant ),
87+ TimestampedValue .of (KV .of (1 , 3 ), instant ))
8188 .advanceWatermarkForNextBatch (instant .plus (Duration .standardSeconds (1L )))
82- .nextBatch (TimestampedValue .of (KV .of (2 , 4 ), instant .plus (Duration .standardSeconds (1L ))))
83- .nextBatch (TimestampedValue .of (KV .of (2 , 5 ), instant .plus (Duration .standardSeconds (1L ))))
84- .nextBatch (TimestampedValue .of (KV .of (2 , 6 ), instant .plus (Duration .standardSeconds (1L ))))
89+ .nextBatch (
90+ TimestampedValue .of (KV .of (2 , 4 ), instant .plus (Duration .standardSeconds (1L ))),
91+ TimestampedValue .of (KV .of (2 , 5 ), instant .plus (Duration .standardSeconds (1L ))),
92+ TimestampedValue .of (KV .of (2 , 6 ), instant .plus (Duration .standardSeconds (1L ))))
8593 .advanceNextBatchWatermarkToInfinity ();
8694 }
8795
@@ -97,19 +105,19 @@ private PTransform<PBegin, PCollection<KV<Integer, Integer>>> createStreamingSou
97105 int value = 1 ;
98106 for (int i = 0 ; i < iterCount ; i ++) {
99107 createStream =
100- createStream
101- . nextBatch ( TimestampedValue .of (KV .of (1 , value ++), instant ))
102- . nextBatch ( TimestampedValue .of (KV .of (1 , value ++), instant ))
103- . nextBatch ( TimestampedValue .of (KV .of (1 , value ++), instant ));
108+ createStream . nextBatch (
109+ TimestampedValue .of (KV .of (1 , value ++), instant ),
110+ TimestampedValue .of (KV .of (1 , value ++), instant ),
111+ TimestampedValue .of (KV .of (1 , value ++), instant ));
104112
105113 instant = instant .plus (Duration .standardSeconds (1L ));
106114 createStream = createStream .advanceWatermarkForNextBatch (instant );
107115
108116 createStream =
109- createStream
110- . nextBatch ( TimestampedValue .of (KV .of (2 , value ++), instant ))
111- . nextBatch ( TimestampedValue .of (KV .of (2 , value ++), instant ))
112- . nextBatch ( TimestampedValue .of (KV .of (2 , value ++), instant ));
117+ createStream . nextBatch (
118+ TimestampedValue .of (KV .of (2 , value ++), instant ),
119+ TimestampedValue .of (KV .of (2 , value ++), instant ),
120+ TimestampedValue .of (KV .of (2 , value ++), instant ));
113121
114122 instant = instant .plus (Duration .standardSeconds (1L ));
115123 createStream = createStream .advanceWatermarkForNextBatch (instant );
@@ -227,6 +235,48 @@ public void process(
227235 }
228236 }
229237
238+ private static class VerifyStatefulOutput
239+ implements SerializableFunction <Iterable <KV <Integer , Integer >>, Void > {
240+ private final List <Integer > expectedKey1 ;
241+ private final List <Integer > expectedKey2 ;
242+
243+ VerifyStatefulOutput (List <Integer > expectedKey1 , List <Integer > expectedKey2 ) {
244+ this .expectedKey1 = expectedKey1 ;
245+ this .expectedKey2 = expectedKey2 ;
246+ }
247+
248+ @ Override
249+ public Void apply (Iterable <KV <Integer , Integer >> input ) {
250+ List <Integer > key1Values = new ArrayList <>();
251+ List <Integer > key2Values = new ArrayList <>();
252+ for (KV <Integer , Integer > kv : input ) {
253+ if (kv .getKey () == 1 ) {
254+ key1Values .add (kv .getValue ());
255+ } else if (kv .getKey () == 2 ) {
256+ key2Values .add (kv .getValue ());
257+ }
258+ }
259+ verifyKey (key1Values , expectedKey1 );
260+ verifyKey (key2Values , expectedKey2 );
261+ return null ;
262+ }
263+
264+ private void verifyKey (List <Integer > actualValues , List <Integer > expectedElements ) {
265+ assertEquals (expectedElements .size (), actualValues .size ());
266+ Collections .sort (actualValues );
267+ List <Integer > differences = new ArrayList <>();
268+ int prev = 0 ;
269+ for (int v : actualValues ) {
270+ differences .add (v - prev );
271+ prev = v ;
272+ }
273+ List <Integer > expectedSorted = new ArrayList <>(expectedElements );
274+ Collections .sort (expectedSorted );
275+ Collections .sort (differences );
276+ assertEquals (expectedSorted , differences );
277+ }
278+ }
279+
230280 @ Category (StreamingTest .class )
231281 @ Test
232282 public void shouldRejectEventTimeTimer () {
@@ -307,15 +357,7 @@ public void shouldProcessGlobalWidowStatefulParDo() {
307357 p .apply (createStreamingSource (p )).apply (ParDo .of (new StatefulDoFn ()));
308358
309359 PAssert .that (result )
310- .containsInAnyOrder (
311- // key 1
312- KV .of (1 , 1 ), // 1
313- KV .of (1 , 3 ), // 1 + 2
314- KV .of (1 , 6 ), // 3 + 3
315- // key 2
316- KV .of (2 , 4 ), // 4
317- KV .of (2 , 9 ), // 4 + 5
318- KV .of (2 , 15 )); // 9 + 6
360+ .satisfies (new VerifyStatefulOutput (Arrays .asList (1 , 2 , 3 ), Arrays .asList (4 , 5 , 6 )));
319361
320362 p .run ().waitUntilFinish ();
321363 }
@@ -328,28 +370,16 @@ public void shouldProcessWindowedStatefulParDo() {
328370 .apply (Window .into (FixedWindows .of (Duration .standardSeconds (1L ))))
329371 .apply (ParDo .of (new StatefulDoFn ()));
330372
373+ IntervalWindow window1 = new IntervalWindow (new Instant (0 ), Duration .standardSeconds (1L ));
374+ IntervalWindow window2 = new IntervalWindow (new Instant (1000 ), Duration .standardSeconds (1L ));
375+
376+ PAssert .that (result )
377+ .inWindow (window1 )
378+ .satisfies (new VerifyStatefulOutput (Arrays .asList (1 , 2 , 3 ), Arrays .asList (4 , 5 , 6 )));
379+
331380 PAssert .that (result )
332- .containsInAnyOrder (
333- // Windowed Key 1
334- KV .of (1 , 1 ), // 1
335- KV .of (1 , 3 ), // 1 + 2
336- KV .of (1 , 6 ), // 3 + 3
337-
338- // Windowed Key 2
339- KV .of (2 , 4 ), // 4
340- KV .of (2 , 9 ), // 4 + 5
341- KV .of (2 , 15 ), // 9 + 6
342-
343- // Windowed Key 1
344- KV .of (1 , 7 ), // 7
345- KV .of (1 , 15 ), // 7 + 8
346- KV .of (1 , 24 ), // 15 + 9
347-
348- // Windowed Key 2
349- KV .of (2 , 10 ), // 10
350- KV .of (2 , 21 ), // 10 + 11
351- KV .of (2 , 33 ) // 21 + 12
352- );
381+ .inWindow (window2 )
382+ .satisfies (new VerifyStatefulOutput (Arrays .asList (7 , 8 , 9 ), Arrays .asList (10 , 11 , 12 )));
353383
354384 p .run ().waitUntilFinish ();
355385 }
0 commit comments