Uploaded image for project: 'Flink'
  1. Flink
  2. FLINK-5972

Don't allow shrinking merging windows

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Blocker
    • Resolution: Fixed
    • Affects Version/s: 1.1.0, 1.2.0, 1.3.0
    • Fix Version/s: 1.3.0, 1.2.1
    • Component/s: DataStream API
    • Labels:
      None

      Description

      A misbehaving MergingWindowAssigner can cause a merge that results in a window that is smaller than the span of all the merged windows. This, in itself is not problematic. It becomes problematic when the end timestamp of a window that was not late before merging is now earlier than the watermark (the timestamp is smaller than the watermark).

      There are two choices:

      • immediately process the window
      • drop the window

      processing the window will lead to late data downstream.

      The current behaviour is to silently drop the window but that logic has a bug: we only remove the dropped window from the MergingWindowSet but we don't properly clean up state and timers that the window still (possibly) has. We should fix this bug in the process of resolving this issue.

      We should either just fix the bug and still silently drop windows or add a check and throw an exception when the end timestamp falls below the watermark.

        Issue Links

          Activity

          Hide
          githubbot ASF GitHub Bot added a comment -

          GitHub user aljoscha opened a pull request:

          https://github.com/apache/flink/pull/3587

          FLINK-5972 Don't allow shrinking merging windows

          Thanks for contributing to Apache Flink. Before you open your pull request, please take the following check list into consideration.
          If your changes take all of the items into account, feel free to open your pull request. For more information and/or questions please refer to the [How To Contribute guide](http://flink.apache.org/how-to-contribute.html).
          In addition to going through the list, please provide a meaningful description of your changes.

          • [ ] General
          • The pull request references the related JIRA issue ("[FLINK-XXX] Jira title text")
          • The pull request addresses only one issue
          • Each commit in the PR has a meaningful commit message (including the JIRA id)
          • [ ] Documentation
          • Documentation has been added for new functionality
          • Old documentation affected by the pull request has been updated
          • JavaDoc for public methods has been added
          • [ ] Tests & Build
          • Functionality added by the pull request is covered by tests
          • `mvn clean verify` has been executed successfully locally or a Travis build has passed

          You can merge this pull request into a Git repository by running:

          $ git pull https://github.com/aljoscha/flink jira-5972-disallow-shrinking-windows

          Alternatively you can review and apply these changes as the patch at:

          https://github.com/apache/flink/pull/3587.patch

          To close this pull request, make a commit to your master/trunk branch
          with (at least) the following in the commit message:

          This closes #3587


          commit d6de53c286db4386166c50c6a3f5871f2b6ed43f
          Author: Aljoscha Krettek <aljoscha.krettek@gmail.com>
          Date: 2017-03-21T13:58:45Z

          FLINK-5972 Don't allow shrinking merging windows

          commit 197f1f95e74fe7557eaead168c88c5b0d8ea2932
          Author: Aljoscha Krettek <aljoscha.krettek@gmail.com>
          Date: 2017-03-21T14:00:24Z

          [hotfix] Add EvictingWindowOperatorContractTest

          This also patches in the missing side output support for
          EvictingWindowOperator.


          Show
          githubbot ASF GitHub Bot added a comment - GitHub user aljoscha opened a pull request: https://github.com/apache/flink/pull/3587 FLINK-5972 Don't allow shrinking merging windows Thanks for contributing to Apache Flink. Before you open your pull request, please take the following check list into consideration. If your changes take all of the items into account, feel free to open your pull request. For more information and/or questions please refer to the [How To Contribute guide] ( http://flink.apache.org/how-to-contribute.html ). In addition to going through the list, please provide a meaningful description of your changes. [ ] General The pull request references the related JIRA issue (" [FLINK-XXX] Jira title text") The pull request addresses only one issue Each commit in the PR has a meaningful commit message (including the JIRA id) [ ] Documentation Documentation has been added for new functionality Old documentation affected by the pull request has been updated JavaDoc for public methods has been added [ ] Tests & Build Functionality added by the pull request is covered by tests `mvn clean verify` has been executed successfully locally or a Travis build has passed You can merge this pull request into a Git repository by running: $ git pull https://github.com/aljoscha/flink jira-5972-disallow-shrinking-windows Alternatively you can review and apply these changes as the patch at: https://github.com/apache/flink/pull/3587.patch To close this pull request, make a commit to your master/trunk branch with (at least) the following in the commit message: This closes #3587 commit d6de53c286db4386166c50c6a3f5871f2b6ed43f Author: Aljoscha Krettek <aljoscha.krettek@gmail.com> Date: 2017-03-21T13:58:45Z FLINK-5972 Don't allow shrinking merging windows commit 197f1f95e74fe7557eaead168c88c5b0d8ea2932 Author: Aljoscha Krettek <aljoscha.krettek@gmail.com> Date: 2017-03-21T14:00:24Z [hotfix] Add EvictingWindowOperatorContractTest This also patches in the missing side output support for EvictingWindowOperator.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user aljoscha commented on the issue:

          https://github.com/apache/flink/pull/3587

          @StephanEwen this is the better alternative to #3535 in that it doesn't silently drop windows and throws an exception when window shrinking occurs.

          Show
          githubbot ASF GitHub Bot added a comment - Github user aljoscha commented on the issue: https://github.com/apache/flink/pull/3587 @StephanEwen this is the better alternative to #3535 in that it doesn't silently drop windows and throws an exception when window shrinking occurs.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107347809

          — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java —
          @@ -574,7 +588,7 @@ private void emitWindowContents(W window, ACC contents) throws Exception {
          *

          • @param element skipped late arriving element to side output
            */
          • private void sideOutput(StreamRecord<IN> element){
            + protected void sideOutput(StreamRecord<IN> element){
              • End diff –

          Should we use the "@VisibleForTesting" annotation, or comment that it is exposed for testing?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107347809 — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java — @@ -574,7 +588,7 @@ private void emitWindowContents(W window, ACC contents) throws Exception { * @param element skipped late arriving element to side output */ private void sideOutput(StreamRecord<IN> element){ + protected void sideOutput(StreamRecord<IN> element){ End diff – Should we use the "@VisibleForTesting" annotation, or comment that it is exposed for testing?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107347975

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          — End diff –

          Let's start trying to avoid star imports in the tests also

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107347975 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; — End diff – Let's start trying to avoid star imports in the tests also
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107350816

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          — End diff –

          Isn't the ordering of side outputs supposed to be deterministic? Why do we need to use `containsInAnyOrder`?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107350816 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); — End diff – Isn't the ordering of side outputs supposed to be deterministic? Why do we need to use `containsInAnyOrder`?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107353469

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception

          { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception

          { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }

          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          — End diff –

          redundant space between `private` and `void`

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107353469 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + } + + @Test + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + } + + + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception { — End diff – redundant space between `private` and `void`
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107355400

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java —
          @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception {
          }

          @Test
          + public void testRejectShrinkingMergingEventTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * A misbehaving

          {@code WindowAssigner}

          can cause a window to become late by merging if
          + * it moves the end-of-window time before the watermark. This verifies that we don't allow that.
          + */
          + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception {
          — End diff –

          This can be private.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107355400 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java — @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception { } @Test + public void testRejectShrinkingMergingEventTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + } + + @Test + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + } + + /** + * A misbehaving {@code WindowAssigner} can cause a window to become late by merging if + * it moves the end-of-window time before the watermark. This verifies that we don't allow that. + */ + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception { — End diff – This can be private.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107348150

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }
          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }
          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector());
          + assertTrue(testHarness.extractOutputStreamRecords().isEmpty());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testOnElementContinue() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // CONTINUE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          +
          + // there should be no firing
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnElementFire() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          + }
          +
          + @Test
          + public void testOnElementFireAndPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers());
          + }
          +
          + @Test
          + public void testOnElementPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer
          +
          + // no output
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnEventTimeContinue() throws Exception

          { + testOnTimeContinue(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeContinue() throws Exception

          { + testOnTimeContinue(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + // this should register two timers because we have two windows
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          +
          + // there should be no firing
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testOnEventTimeFire() throws Exception

          { + testOnTimeFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFire() throws Exception

          { + testOnTimeFire(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testOnEventTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          + }
          +
          + @Test
          + public void testOnEventTimePurge() throws Exception

          { + testOnTimePurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimePurge() throws Exception

          { + testOnTimePurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          +
          + // still no output
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for a non-existent window fires.
          + */
          + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }
          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + }
          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for an empty merging window.
          + */
          + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + }

          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * fires for a merging window that was already garbage collected.
          + */
          + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + // this should trigger GC
          + timeAdaptor.advanceTime(testHarness, 4L);
          +
          + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext());
          +
          + assertEquals(0, testHarness.numKeyedStateEntries());
          + // we still have a dangling timer because our trigger doesn't do cleanup
          + assertEquals(1, timeAdaptor.numTimers(testHarness));
          +
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + // now we trigger the dangling timer
          + timeAdaptor.advanceTime(testHarness, 10L);
          +
          + // we don't fire again
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          + }
          +
          + @Test
          + public void testEventTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + }

          +
          + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeTimerFiring() throws Exception

          { + testTimerFiring(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerFiring() throws Exception

          { + testTimerFiring(new ProcessingTimeAdaptor()); + }

          +
          +
          + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + }

          +
          + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 2); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.advanceTime(testHarness, 50L); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(0), 1L, null); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), 2L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + }

          +
          + @Test
          + public void testMergeWindowsIsCalled() throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4))), anyMergeCallback());
          + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4), new TimeWindow(0, 2))), anyMergeCallback());
          + verify(mockAssigner, times(2)).mergeWindows(anyCollection(), anyMergeCallback());
          +
          +
          — End diff –

          too many unnecessary empty lines here

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107348150 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + } + + @Test + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + } + + + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector()); + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + timeAdaptor.advanceTime(testHarness, 1L); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testOnElementContinue() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // CONTINUE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + + // there should be no firing + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnElementFire() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + } + + @Test + public void testOnElementFireAndPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); + } + + @Test + public void testOnElementPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer + + // no output + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnEventTimeContinue() throws Exception { + testOnTimeContinue(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeContinue() throws Exception { + testOnTimeContinue(new ProcessingTimeAdaptor()); + } + + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + // this should register two timers because we have two windows + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + + // there should be no firing + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testOnEventTimeFire() throws Exception { + testOnTimeFire(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFire() throws Exception { + testOnTimeFire(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testOnEventTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + } + + @Test + public void testOnEventTimePurge() throws Exception { + testOnTimePurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimePurge() throws Exception { + testOnTimePurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 1L); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + + // still no output + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testNoEventTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + } + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for a non-existent window fires. + */ + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for an empty merging window. + */ + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * fires for a merging window that was already garbage collected. + */ + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + // this should trigger GC + timeAdaptor.advanceTime(testHarness, 4L); + + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext()); + + assertEquals(0, testHarness.numKeyedStateEntries()); + // we still have a dangling timer because our trigger doesn't do cleanup + assertEquals(1, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + // now we trigger the dangling timer + timeAdaptor.advanceTime(testHarness, 10L); + + // we don't fire again + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + } + + @Test + public void testEventTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + } + + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeTimerFiring() throws Exception { + testTimerFiring(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerFiring() throws Exception { + testTimerFiring(new ProcessingTimeAdaptor()); + } + + + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + } + + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 2); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.advanceTime(testHarness, 50L); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(0), 1L, null); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), 2L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + } + + @Test + public void testMergeWindowsIsCalled() throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4))), anyMergeCallback()); + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4), new TimeWindow(0, 2))), anyMergeCallback()); + verify(mockAssigner, times(2)).mergeWindows(anyCollection(), anyMergeCallback()); + + — End diff – too many unnecessary empty lines here
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107346963

          — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperator.java —
          @@ -122,6 +121,20 @@ public void processElement(StreamRecord<IN> element) throws Exception {
          public void merge(W mergeResult,
          Collection<W> mergedWindows, W stateWindowResult,
          Collection<W> mergedStateWindows) throws Exception {
          +
          + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark()))

          { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + }

          else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) {
          + throw new UnsupportedOperationException("The end timestamp of a " +
          + "processing-time window cannot become earlier than the current procesing time" +
          + "by merging. Current processing time: " + internalTimerService.currentProcessingTime() +
          + " window: " + mergeResult);
          +
          — End diff –

          unnecessary empty line

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107346963 — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperator.java — @@ -122,6 +121,20 @@ public void processElement(StreamRecord<IN> element) throws Exception { public void merge(W mergeResult, Collection<W> mergedWindows, W stateWindowResult, Collection<W> mergedStateWindows) throws Exception { + + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark())) { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + } else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) { + throw new UnsupportedOperationException("The end timestamp of a " + + "processing-time window cannot become earlier than the current procesing time" + + "by merging. Current processing time: " + internalTimerService.currentProcessingTime() + + " window: " + mergeResult); + — End diff – unnecessary empty line
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107354200

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }
          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }
          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector());
          + assertTrue(testHarness.extractOutputStreamRecords().isEmpty());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testOnElementContinue() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // CONTINUE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          +
          + // there should be no firing
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnElementFire() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          + }
          +
          + @Test
          + public void testOnElementFireAndPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers());
          + }
          +
          + @Test
          + public void testOnElementPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer
          +
          + // no output
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnEventTimeContinue() throws Exception

          { + testOnTimeContinue(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeContinue() throws Exception

          { + testOnTimeContinue(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + // this should register two timers because we have two windows
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          +
          + // there should be no firing
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testOnEventTimeFire() throws Exception

          { + testOnTimeFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFire() throws Exception

          { + testOnTimeFire(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testOnEventTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          + }
          +
          + @Test
          + public void testOnEventTimePurge() throws Exception

          { + testOnTimePurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimePurge() throws Exception

          { + testOnTimePurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          +
          + // still no output
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for a non-existent window fires.
          + */
          + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception

          { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception

          { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + }

          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for an empty merging window.
          — End diff –

          seems like a missing word here: "if a timer fires for an empty merging window"

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107354200 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + } + + @Test + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + } + + + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector()); + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + timeAdaptor.advanceTime(testHarness, 1L); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testOnElementContinue() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // CONTINUE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + + // there should be no firing + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnElementFire() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + } + + @Test + public void testOnElementFireAndPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); + } + + @Test + public void testOnElementPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer + + // no output + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnEventTimeContinue() throws Exception { + testOnTimeContinue(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeContinue() throws Exception { + testOnTimeContinue(new ProcessingTimeAdaptor()); + } + + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + // this should register two timers because we have two windows + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + + // there should be no firing + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testOnEventTimeFire() throws Exception { + testOnTimeFire(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFire() throws Exception { + testOnTimeFire(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testOnEventTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + } + + @Test + public void testOnEventTimePurge() throws Exception { + testOnTimePurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimePurge() throws Exception { + testOnTimePurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 1L); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + + // still no output + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testNoEventTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + } + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for a non-existent window fires. + */ + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for an empty merging window. — End diff – seems like a missing word here: "if a timer fires for an empty merging window"
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107350215

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          — End diff –

          I think there was some sort of agreement in avoiding Guava in Flink.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107350215 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; — End diff – I think there was some sort of agreement in avoiding Guava in Flink.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107346816

          — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperator.java —
          @@ -122,6 +121,20 @@ public void processElement(StreamRecord<IN> element) throws Exception {
          public void merge(W mergeResult,
          Collection<W> mergedWindows, W stateWindowResult,
          Collection<W> mergedStateWindows) throws Exception {
          +
          + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark()))

          { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + }

          else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) {
          + throw new UnsupportedOperationException("The end timestamp of a " +
          + "processing-time window cannot become earlier than the current procesing time" +
          — End diff –

          Missing space between "... processing time" and " by merging".

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107346816 — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperator.java — @@ -122,6 +121,20 @@ public void processElement(StreamRecord<IN> element) throws Exception { public void merge(W mergeResult, Collection<W> mergedWindows, W stateWindowResult, Collection<W> mergedStateWindows) throws Exception { + + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark())) { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + } else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) { + throw new UnsupportedOperationException("The end timestamp of a " + + "processing-time window cannot become earlier than the current procesing time" + — End diff – Missing space between "... processing time" and " by merging".
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107352734

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception {
          + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE);
          — End diff –

          can we resuse `anyTimeWindow()` to replace `Matchers.<TimeWindow>any()`, just because we've defined the method in this class anyways ?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107352734 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); — End diff – can we resuse `anyTimeWindow()` to replace `Matchers.<TimeWindow>any()`, just because we've defined the method in this class anyways ?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107353123

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          — End diff –

          Also, should we consider moving the static mock / matcher util methods to a separate class, similar to the `StreamRecordMatchers` class?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107353123 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { — End diff – Also, should we consider moving the static mock / matcher util methods to a separate class, similar to the `StreamRecordMatchers` class?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107349684

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          — End diff –

          nitpick: The line spacing between the tests / methods in this class is also generally a bit off, occasionally there's 2-line spacings.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107349684 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { — End diff – nitpick: The line spacing between the tests / methods in this class is also generally a bit off, occasionally there's 2-line spacings.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107349222

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          — End diff –

          type: I think it's "late-data"

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107349222 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does — End diff – type: I think it's "late-data"
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107356162

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java —
          @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception {
          }

          @Test
          + public void testRejectShrinkingMergingEventTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * A misbehaving

          {@code WindowAssigner}

          can cause a window to become late by merging if
          + * it moves the end-of-window time before the watermark. This verifies that we don't allow that.
          + */
          + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, 5);
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[0]; + Trigger.OnMergeContext context = (Trigger.OnMergeContext) invocation.getArguments()[1]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onMerge(anyTimeWindow(), anyOnMergeContext());
          +
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[0]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[1]; + timeAdaptor.deleteTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).clear(); + return null; + }

          + }).when(mockTrigger).clear(anyTimeWindow(), anyTriggerContext());
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(8, 11)));
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(3, testHarness.numKeyedStateEntries()); // window state plus trigger state plus merging window set
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // cleanup timer == end-of-window timer
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(10, 13)));
          +
          + shouldMergeWindows(
          + mockAssigner,
          + Lists.newArrayList(new TimeWindow(8, 11), new TimeWindow(10, 13)),
          — End diff –

          Avoid Guava.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107356162 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java — @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception { } @Test + public void testRejectShrinkingMergingEventTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + } + + @Test + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + } + + /** + * A misbehaving {@code WindowAssigner} can cause a window to become late by merging if + * it moves the end-of-window time before the watermark. This verifies that we don't allow that. + */ + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, 5); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[0]; + Trigger.OnMergeContext context = (Trigger.OnMergeContext) invocation.getArguments()[1]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onMerge(anyTimeWindow(), anyOnMergeContext()); + + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[0]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[1]; + timeAdaptor.deleteTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).clear(); + return null; + } + }).when(mockTrigger).clear(anyTimeWindow(), anyTriggerContext()); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(8, 11))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, testHarness.numKeyedStateEntries()); // window state plus trigger state plus merging window set + assertEquals(1, timeAdaptor.numTimers(testHarness)); // cleanup timer == end-of-window timer + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(10, 13))); + + shouldMergeWindows( + mockAssigner, + Lists.newArrayList(new TimeWindow(8, 11), new TimeWindow(10, 13)), — End diff – Avoid Guava.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107347411

          — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java —
          @@ -352,6 +352,20 @@ public void processElement(StreamRecord<IN> element) throws Exception {
          public void merge(W mergeResult,
          Collection<W> mergedWindows, W stateWindowResult,
          Collection<W> mergedStateWindows) throws Exception {
          +
          + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark()))

          { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + }

          else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) {
          + throw new UnsupportedOperationException("The end timestamp of a " +
          + "processing-time window cannot become earlier than the current procesing time" +
          — End diff –

          Missing space between "... processing time" and " by merging".

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107347411 — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java — @@ -352,6 +352,20 @@ public void processElement(StreamRecord<IN> element) throws Exception { public void merge(W mergeResult, Collection<W> mergedWindows, W stateWindowResult, Collection<W> mergedStateWindows) throws Exception { + + if ((windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentWatermark())) { + throw new UnsupportedOperationException("The end timestamp of an " + + "event-time window cannot become earlier than the current watermark " + + "by merging. Current watermark: " + internalTimerService.currentWatermark() + + " window: " + mergeResult); + } else if (!windowAssigner.isEventTime() && mergeResult.maxTimestamp() <= internalTimerService.currentProcessingTime()) { + throw new UnsupportedOperationException("The end timestamp of a " + + "processing-time window cannot become earlier than the current procesing time" + — End diff – Missing space between "... processing time" and " by merging".
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107347802

          — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java —
          @@ -138,7 +138,7 @@

          • {@code window.maxTimestamp + allowedLateness}

            is smaller than the current watermark will

          • be emitted to this.
            */
          • private final OutputTag<IN> lateDataOutputTag;
            + protected final OutputTag<IN> lateDataOutputTag;
              • End diff –

          Should we use the "@VisibleForTesting" annotation, or comment that it is exposed for testing?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107347802 — Diff: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperator.java — @@ -138,7 +138,7 @@ {@code window.maxTimestamp + allowedLateness} is smaller than the current watermark will be emitted to this. */ private final OutputTag<IN> lateDataOutputTag; + protected final OutputTag<IN> lateDataOutputTag; End diff – Should we use the "@VisibleForTesting" annotation, or comment that it is exposed for testing?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107355610

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java —
          @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception {
          }

          @Test
          + public void testRejectShrinkingMergingEventTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception

          { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * A misbehaving

          {@code WindowAssigner}

          can cause a window to become late by merging if
          + * it moves the end-of-window time before the watermark. This verifies that we don't allow that.
          + */
          + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, 5);
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          — End diff –

          I wonder if we want to have a common class to contain all these static mock util methods for these kind of bigger stubs too.
          Together with `EvictingWindowOperatorContractTest` that could save quite a bit of LoC and easier to read.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107355610 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/WindowOperatorContractTest.java — @@ -1577,6 +1585,91 @@ public TriggerResult answer(InvocationOnMock invocation) throws Exception { } @Test + public void testRejectShrinkingMergingEventTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new EventTimeAdaptor()); + } + + @Test + public void testRejectShrinkingMergingProcessingTimeWindows() throws Exception { + testRejectShrinkingMergingWindows(new ProcessingTimeAdaptor()); + } + + /** + * A misbehaving {@code WindowAssigner} can cause a window to become late by merging if + * it moves the end-of-window time before the watermark. This verifies that we don't allow that. + */ + public void testRejectShrinkingMergingWindows(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, 5); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, window.maxTimestamp()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); — End diff – I wonder if we want to have a common class to contain all these static mock util methods for these kind of bigger stubs too. Together with `EvictingWindowOperatorContractTest` that could save quite a bit of LoC and easier to read.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107352208

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          — End diff –

          Why not reuse `shouldFireOnElement` here?

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107352208 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); — End diff – Why not reuse `shouldFireOnElement` here?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107348912

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          — End diff –

          nitpick: For this class, can we place all the private static test and mock utilities before the actual @Tests?
          Just a habit of putting the actual working public stuff before privates and utilities, isn't necessary if you don't agree

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107348912 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { — End diff – nitpick: For this class, can we place all the private static test and mock utilities before the actual @Tests? Just a habit of putting the actual working public stuff before privates and utilities, isn't necessary if you don't agree
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107354721

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }
          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }
          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector());
          + assertTrue(testHarness.extractOutputStreamRecords().isEmpty());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testOnElementContinue() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // CONTINUE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          +
          + // there should be no firing
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnElementFire() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          + }
          +
          + @Test
          + public void testOnElementFireAndPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers());
          + }
          +
          + @Test
          + public void testOnElementPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer
          +
          + // no output
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnEventTimeContinue() throws Exception

          { + testOnTimeContinue(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeContinue() throws Exception

          { + testOnTimeContinue(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + // this should register two timers because we have two windows
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          +
          + // there should be no firing
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testOnEventTimeFire() throws Exception

          { + testOnTimeFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFire() throws Exception

          { + testOnTimeFire(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testOnEventTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          + }
          +
          + @Test
          + public void testOnEventTimePurge() throws Exception

          { + testOnTimePurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimePurge() throws Exception

          { + testOnTimePurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          +
          + // still no output
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for a non-existent window fires.
          + */
          + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }
          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + }
          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for an empty merging window.
          + */
          + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + }

          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * fires for a merging window that was already garbage collected.
          + */
          + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + // this should trigger GC
          + timeAdaptor.advanceTime(testHarness, 4L);
          +
          + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext());
          +
          + assertEquals(0, testHarness.numKeyedStateEntries());
          + // we still have a dangling timer because our trigger doesn't do cleanup
          + assertEquals(1, timeAdaptor.numTimers(testHarness));
          +
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + // now we trigger the dangling timer
          + timeAdaptor.advanceTime(testHarness, 10L);
          +
          + // we don't fire again
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          + }
          +
          + @Test
          + public void testEventTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + }

          +
          + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeTimerFiring() throws Exception

          { + testTimerFiring(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerFiring() throws Exception

          { + testTimerFiring(new ProcessingTimeAdaptor()); + }

          +
          +
          + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + }

          +
          + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception {
          — End diff –

          This can be private.

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107354721 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + } + + @Test + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + } + + + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector()); + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + timeAdaptor.advanceTime(testHarness, 1L); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testOnElementContinue() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // CONTINUE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + + // there should be no firing + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnElementFire() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + } + + @Test + public void testOnElementFireAndPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); + } + + @Test + public void testOnElementPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer + + // no output + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnEventTimeContinue() throws Exception { + testOnTimeContinue(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeContinue() throws Exception { + testOnTimeContinue(new ProcessingTimeAdaptor()); + } + + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + // this should register two timers because we have two windows + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + + // there should be no firing + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testOnEventTimeFire() throws Exception { + testOnTimeFire(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFire() throws Exception { + testOnTimeFire(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testOnEventTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + } + + @Test + public void testOnEventTimePurge() throws Exception { + testOnTimePurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimePurge() throws Exception { + testOnTimePurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 1L); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + + // still no output + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testNoEventTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + } + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for a non-existent window fires. + */ + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for an empty merging window. + */ + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * fires for a merging window that was already garbage collected. + */ + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + // this should trigger GC + timeAdaptor.advanceTime(testHarness, 4L); + + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext()); + + assertEquals(0, testHarness.numKeyedStateEntries()); + // we still have a dangling timer because our trigger doesn't do cleanup + assertEquals(1, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + // now we trigger the dangling timer + timeAdaptor.advanceTime(testHarness, 10L); + + // we don't fire again + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + } + + @Test + public void testEventTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + } + + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeTimerFiring() throws Exception { + testTimerFiring(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerFiring() throws Exception { + testTimerFiring(new ProcessingTimeAdaptor()); + } + + + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + } + + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception { — End diff – This can be private.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107354758

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }
          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }
          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector());
          + assertTrue(testHarness.extractOutputStreamRecords().isEmpty());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testOnElementContinue() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // CONTINUE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          +
          + // there should be no firing
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnElementFire() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          + }
          +
          + @Test
          + public void testOnElementFireAndPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers());
          + }
          +
          + @Test
          + public void testOnElementPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer
          +
          + // no output
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnEventTimeContinue() throws Exception

          { + testOnTimeContinue(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeContinue() throws Exception

          { + testOnTimeContinue(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + // this should register two timers because we have two windows
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          +
          + // there should be no firing
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testOnEventTimeFire() throws Exception

          { + testOnTimeFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFire() throws Exception

          { + testOnTimeFire(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testOnEventTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          + }
          +
          + @Test
          + public void testOnEventTimePurge() throws Exception

          { + testOnTimePurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimePurge() throws Exception

          { + testOnTimePurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          +
          + // still no output
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for a non-existent window fires.
          + */
          + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }
          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + }
          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for an empty merging window.
          + */
          + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + }

          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * fires for a merging window that was already garbage collected.
          + */
          + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + // this should trigger GC
          + timeAdaptor.advanceTime(testHarness, 4L);
          +
          + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext());
          +
          + assertEquals(0, testHarness.numKeyedStateEntries());
          + // we still have a dangling timer because our trigger doesn't do cleanup
          + assertEquals(1, timeAdaptor.numTimers(testHarness));
          +
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + // now we trigger the dangling timer
          + timeAdaptor.advanceTime(testHarness, 10L);
          +
          + // we don't fire again
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          + }
          +
          + @Test
          + public void testEventTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + }

          +
          + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeTimerFiring() throws Exception

          { + testTimerFiring(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerFiring() throws Exception

          { + testTimerFiring(new ProcessingTimeAdaptor()); + }

          +
          +
          + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + }

          +
          + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 2); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.advanceTime(testHarness, 50L); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(0), 1L, null); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), 2L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + }

          +
          + @Test
          + public void testMergeWindowsIsCalled() throws Exception

          { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4))), anyMergeCallback()); + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4), new TimeWindow(0, 2))), anyMergeCallback()); + verify(mockAssigner, times(2)).mergeWindows(anyCollection(), anyMergeCallback()); + + + }

          +
          + @Test
          + public void testEventTimeWindowsAreMergedEagerly() throws Exception

          { + testWindowsAreMergedEagerly(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeWindowsAreMergedEagerly() throws Exception

          { + testWindowsAreMergedEagerly(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that windows are merged eagerly, if possible.
          + */
          + public void testWindowsAreMergedEagerly(final TimeDomainAdaptor timeAdaptor) throws Exception {
          — End diff –

          Private

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107354758 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + /** + * Verify that there is no late-date side output if the {@code WindowAssigner} does + * not assign any windows. + */ + @Test + public void testNoLateSideOutputForSkippedWindows() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processWatermark(0); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty()); + } + + @Test + public void testLateSideOutput() throws Exception { + + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){}; + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processWatermark(20); + testHarness.processElement(new StreamRecord<>(0, 5L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L))); + + // we should also see side output if the WindowAssigner assigns no windows + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.<TimeWindow>emptyList()); + + testHarness.processElement(new StreamRecord<>(0, 10L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext()); + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext()); + + assertThat(testHarness.getSideOutput(lateOutputTag), + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L))); + + } + + + @Test + public void testAssignerIsInvokedOncePerElement() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + } + + @Test + public void testAssignerWithMultipleWindows() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testWindowsDontInterfere() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + } + + @Test + public void testOnElementCalledPerWindow() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @Test + public void testEmittingFromWindowFunction() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + } + + @Test + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + } + + + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + } + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector()); + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + timeAdaptor.advanceTime(testHarness, 1L); + + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector()); + + assertThat(testHarness.extractOutputStreamRecords(), + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L))); + } + + @Test + public void testOnElementContinue() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // CONTINUE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + + // there should be no firing + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnElementFire() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers + } + + @Test + public void testOnElementFireAndPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); + } + + @Test + public void testOnElementPurge() throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time + + // timers will stick around + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer + + // no output + assertEquals(0, testHarness.getOutput().size()); + } + + @Test + public void testOnEventTimeContinue() throws Exception { + testOnTimeContinue(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeContinue() throws Exception { + testOnTimeContinue(new ProcessingTimeAdaptor()); + } + + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + // this should register two timers because we have two windows + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + + // there should be no firing + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testOnEventTimeFire() throws Exception { + testOnTimeFire(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFire() throws Exception { + testOnTimeFire(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE should not purge contents + assertEquals(4, testHarness.numKeyedStateEntries()); + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testOnEventTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimeFireAndPurge() throws Exception { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 0L); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // FIRE_AND_PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + } + + @Test + public void testOnEventTimePurge() throws Exception { + testOnTimePurge(new EventTimeAdaptor()); + } + + @Test + public void testOnProcessingTimePurge() throws Exception { + testOnTimePurge(new ProcessingTimeAdaptor()); + } + + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + timeAdaptor.shouldPurgeOnTime(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows + + timeAdaptor.advanceTime(testHarness, 1L); + + // clear is only called at cleanup time/GC time + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext()); + + // PURGE should purge contents + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there + + // still no output + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + } + + @Test + public void testNoEventTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + } + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for a non-existent window fires. + */ + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * for an empty merging window. + */ + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.advanceTime(testHarness, 0L); + + // trigger is not called if there is no more window (timer is silently ignored) + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left + } + + @Test + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + } + + @Test + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + } + + + /** + * Verify that we neither invoke the trigger nor the window function if a timer + * fires for a merging window that was already garbage collected. + */ + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + + @SuppressWarnings("unchecked") + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction = + mock(InternalWindowFunction.class); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4))); + + assertEquals(0, testHarness.extractOutputStreamRecords().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + } + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer + + timeAdaptor.shouldContinueOnTime(mockTrigger); + + // this should trigger GC + timeAdaptor.advanceTime(testHarness, 4L); + + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext()); + + assertEquals(0, testHarness.numKeyedStateEntries()); + // we still have a dangling timer because our trigger doesn't do cleanup + assertEquals(1, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + verify(mockWindowFunction, never()) + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector()); + + // now we trigger the dangling timer + timeAdaptor.advanceTime(testHarness, 10L); + + // we don't fire again + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + } + + @Test + public void testEventTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerCreationAndDeletion() throws Exception { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + } + + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeTimerFiring() throws Exception { + testTimerFiring(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeTimerFiring() throws Exception { + testTimerFiring(new ProcessingTimeAdaptor()); + } + + + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + } + + @Test + public void testEventTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + } + + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 2); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.advanceTime(testHarness, 50L); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(0), 1L, null); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), 2L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + } + + @Test + public void testMergeWindowsIsCalled() throws Exception { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4))), anyMergeCallback()); + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4), new TimeWindow(0, 2))), anyMergeCallback()); + verify(mockAssigner, times(2)).mergeWindows(anyCollection(), anyMergeCallback()); + + + } + + @Test + public void testEventTimeWindowsAreMergedEagerly() throws Exception { + testWindowsAreMergedEagerly(new EventTimeAdaptor()); + } + + @Test + public void testProcessingTimeWindowsAreMergedEagerly() throws Exception { + testWindowsAreMergedEagerly(new ProcessingTimeAdaptor()); + } + + /** + * Verify that windows are merged eagerly, if possible. + */ + public void testWindowsAreMergedEagerly(final TimeDomainAdaptor timeAdaptor) throws Exception { — End diff – Private
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user tzulitai commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3587#discussion_r107351397

          — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java —
          @@ -0,0 +1,2654 @@
          +/*
          + * Licensed to the Apache Software Foundation (ASF) under one
          + * or more contributor license agreements. See the NOTICE file
          + * distributed with this work for additional information
          + * regarding copyright ownership. The ASF licenses this file
          + * to you 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
          + * <p>
          + * http://www.apache.org/licenses/LICENSE-2.0
          + * <p>
          + * 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 org.apache.flink.streaming.runtime.operators.windowing;
          +
          +
          +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord;
          +import static org.hamcrest.Matchers.containsInAnyOrder;
          +import static org.junit.Assert.*;
          +import static org.mockito.Matchers.anyLong;
          +import static org.mockito.Mockito.*;
          +
          +import com.google.common.collect.Lists;
          +import java.util.Arrays;
          +import java.util.Collection;
          +import java.util.Collections;
          +import java.util.List;
          +import org.apache.flink.api.common.ExecutionConfig;
          +import org.apache.flink.api.common.functions.FoldFunction;
          +import org.apache.flink.api.common.functions.ReduceFunction;
          +import org.apache.flink.api.common.state.AppendingState;
          +import org.apache.flink.api.common.state.FoldingStateDescriptor;
          +import org.apache.flink.api.common.state.ListState;
          +import org.apache.flink.api.common.state.ListStateDescriptor;
          +import org.apache.flink.api.common.state.ReducingStateDescriptor;
          +import org.apache.flink.api.common.state.StateDescriptor;
          +import org.apache.flink.api.common.state.ValueStateDescriptor;
          +import org.apache.flink.api.common.typeinfo.BasicTypeInfo;
          +import org.apache.flink.api.common.typeutils.TypeSerializer;
          +import org.apache.flink.api.common.typeutils.base.IntSerializer;
          +import org.apache.flink.api.common.typeutils.base.StringSerializer;
          +import org.apache.flink.api.java.functions.KeySelector;
          +import org.apache.flink.streaming.api.watermark.Watermark;
          +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner;
          +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner;
          +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor;
          +import org.apache.flink.streaming.api.windowing.triggers.Trigger;
          +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult;
          +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow;
          +import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
          +import org.apache.flink.streaming.api.windowing.windows.Window;
          +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer;
          +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
          +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles;
          +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness;
          +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
          +import org.apache.flink.util.Collector;
          +import org.apache.flink.util.OutputTag;
          +import org.apache.flink.util.TestLogger;
          +import org.junit.Rule;
          +import org.junit.Test;
          +import org.junit.rules.ExpectedException;
          +import org.mockito.Matchers;
          +import org.mockito.Mockito;
          +import org.mockito.invocation.InvocationOnMock;
          +import org.mockito.stubbing.Answer;
          +import org.mockito.verification.VerificationMode;
          +
          +/**
          + * These tests verify that

          {@link WindowOperator}

          correctly interacts with the other windowing
          + * components:

          {@link WindowAssigner}

          ,
          + *

          {@link Trigger}

          .
          + *

          {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction}

          and window state.
          + *
          + * <p>These tests document the implicit contract that exists between the windowing components.
          + *
          + * <p><b>Important:</b>This test must always be kept up-to-date with
          + *

          {@link WindowOperatorContractTest}

          .
          + */
          +public class EvictingWindowOperatorContractTest extends TestLogger {
          +
          + @Rule
          + public ExpectedException expectedException = ExpectedException.none();
          +
          + private static ValueStateDescriptor<String> valueStateDescriptor =
          + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null);
          +
          + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor =
          + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE));
          +
          + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception

          { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + }

          +
          + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception

          { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + }

          +
          + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + }

          +
          +
          + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception

          { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + }

          +
          +
          + static WindowAssigner.WindowAssignerContext anyAssignerContext()

          { + return Mockito.any(); + }
          +
          + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + }

          +
          + static <T> Collector<T> anyCollector()

          { + return Mockito.any(); + }
          +
          + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + }

          +
          + @SuppressWarnings("unchecked")
          + static Iterable<Integer> intIterable(Integer... values)

          { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + }

          +
          + static TimeWindow anyTimeWindow()

          { + return Mockito.any(); + }
          +
          + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + }

          +
          + static MergingWindowAssigner.MergeCallback anyMergeCallback()

          { + return Mockito.any(); + }

          +
          +
          + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception {
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + }

          + })
          + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          + }
          +
          + @SuppressWarnings("unchecked")
          + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) {
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + }

          + })
          + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject());
          + }
          +
          + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + private static <T> void shouldContinueOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + }

          +
          + private static <T> void shouldFireOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + }

          +
          + private static <T> void shouldPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + }

          +
          + private static <T> void shouldFireAndPurgeOnProcessingTime(Trigger<T, TimeWindow> mockTrigger) throws Exception

          { + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + }

          +
          + /**
          + * Verify that there is no late-date side output if the

          {@code WindowAssigner}

          does
          + * not assign any windows.
          + */
          + @Test
          + public void testNoLateSideOutputForSkippedWindows() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processWatermark(0);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertTrue(testHarness.getSideOutput(lateOutputTag) == null || testHarness.getSideOutput(lateOutputTag).isEmpty());
          + }
          +
          + @Test
          + public void testLateSideOutput() throws Exception {
          +
          + OutputTag<Integer> lateOutputTag = new OutputTag<Integer>("late"){};
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + OneInputStreamOperatorTestHarness<Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction, lateOutputTag);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 0)));
          +
          + testHarness.processWatermark(20);
          + testHarness.processElement(new StreamRecord<>(0, 5L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L)));
          +
          + // we should also see side output if the WindowAssigner assigns no windows
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.<TimeWindow>emptyList());
          +
          + testHarness.processElement(new StreamRecord<>(0, 10L));
          +
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(5L), anyAssignerContext());
          + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(10L), anyAssignerContext());
          +
          + assertThat(testHarness.getSideOutput(lateOutputTag),
          + containsInAnyOrder(isStreamRecord(0, 5L), isStreamRecord(0, 10L)));
          +
          + }
          +
          +
          + @Test
          + public void testAssignerIsInvokedOncePerElement() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 0))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(1)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner, times(2)).assignWindows(eq(0), eq(0L), anyAssignerContext()); + + }

          +
          + @Test
          + public void testAssignerWithMultipleWindows() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + shouldFireOnElement(mockTrigger); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testWindowsDontInterfere() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + // no output so far + assertTrue(testHarness.extractOutputStreamRecords().isEmpty()); + + // state for two windows + assertEquals(2, testHarness.numKeyedStateEntries()); + assertEquals(2, testHarness.numEventTimeTimers()); + + // now we fire + shouldFireOnElement(mockTrigger); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 1))); + + testHarness.processElement(new StreamRecord<>(1, 0L)); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Collections.singletonList(new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockWindowFunction, times(2)).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0, 0), EvictingWindowOperatorContractTest.<Void>anyCollector()); + verify(mockWindowFunction, times(1)).apply(eq(1), eq(new TimeWindow(0, 1)), intIterable(1, 1), EvictingWindowOperatorContractTest.<Void>anyCollector()); + }

          +
          + @Test
          + public void testOnElementCalledPerWindow() throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + OneInputStreamOperatorTestHarness<Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + testHarness.processElement(new StreamRecord<>(42, 1L)); + + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(2, 4)), anyTriggerContext()); + verify(mockTrigger).onElement(eq(42), eq(1L), eq(new TimeWindow(0, 2)), anyTriggerContext()); + + verify(mockTrigger, times(2)).onElement(anyInt(), anyLong(), anyTimeWindow(), anyTriggerContext()); + }

          +
          + @Test
          + public void testEmittingFromWindowFunction() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception

          { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }
          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnEventTime() throws Exception { + testEmittingFromWindowFunction(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testEmittingFromWindowFunctionOnProcessingTime() throws Exception { + testEmittingFromWindowFunction(new ProcessingTimeAdaptor()); + }
          +
          +
          + private void testEmittingFromWindowFunction(TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, String, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, String> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Collections.singletonList(new TimeWindow(0, 2)));
          +
          + doAnswer(new Answer<Void>() {
          + @Override
          + public Void answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Collector<String> out = invocation.getArgumentAt(3, Collector.class); + out.collect("Hallo"); + out.collect("Ciao"); + return null; + }

          + }).when(mockWindowFunction).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, never()).apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<String>anyCollector());
          + assertTrue(testHarness.extractOutputStreamRecords().isEmpty());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<String>anyCollector());
          +
          + assertThat(testHarness.extractOutputStreamRecords(),
          + containsInAnyOrder(isStreamRecord("Hallo", 1L), isStreamRecord("Ciao", 1L)));
          + }
          +
          + @Test
          + public void testOnElementContinue() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // CONTINUE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          +
          + // there should be no firing
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnElementFire() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window contents plus trigger state
          + assertEquals(4, testHarness.numEventTimeTimers()); // window timers/gc timers
          + }
          +
          + @Test
          + public void testOnElementFireAndPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + TimeWindow window = (TimeWindow) invocation.getArguments()[2]; + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(window.getEnd()); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.FIRE_AND_PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers());
          + }
          +
          + @Test
          + public void testOnElementPurge() throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.getOutput().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around until GC time
          +
          + // timers will stick around
          + assertEquals(4, testHarness.numEventTimeTimers()); // trigger timer and GC timer
          +
          + // no output
          + assertEquals(0, testHarness.getOutput().size());
          + }
          +
          + @Test
          + public void testOnEventTimeContinue() throws Exception

          { + testOnTimeContinue(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeContinue() throws Exception

          { + testOnTimeContinue(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeContinue(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + // this should register two timers because we have two windows
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // we don't want to fire the cleanup timer + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents plus trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          +
          + // there should be no firing
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testOnEventTimeFire() throws Exception

          { + testOnTimeFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFire() throws Exception

          { + testOnTimeFire(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFire(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE should not purge contents
          + assertEquals(4, testHarness.numKeyedStateEntries());
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testOnEventTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimeFireAndPurge() throws Exception

          { + testOnTimeFireAndPurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimeFireAndPurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldFireAndPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + verify(mockWindowFunction, times(2)).apply(eq(0), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(0, 2)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          + verify(mockWindowFunction, times(1)).apply(eq(0), eq(new TimeWindow(2, 4)), intIterable(0), EvictingWindowOperatorContractTest.<Void>anyCollector());
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // FIRE_AND_PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state stays until GC time
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          + }
          +
          + @Test
          + public void testOnEventTimePurge() throws Exception

          { + testOnTimePurge(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testOnProcessingTimePurge() throws Exception

          { + testOnTimePurge(new ProcessingTimeAdaptor()); + }

          +
          + private void testOnTimePurge(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(4, 6)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 1L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + timeAdaptor.shouldPurgeOnTime(mockTrigger);
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(4, testHarness.numKeyedStateEntries()); // window-contents and trigger state for two windows
          + assertEquals(4, timeAdaptor.numTimers(testHarness)); // timers/gc timers for two windows
          +
          + timeAdaptor.advanceTime(testHarness, 1L);
          +
          + // clear is only called at cleanup time/GC time
          + verify(mockTrigger, never()).clear(anyTimeWindow(), anyTriggerContext());
          +
          + // PURGE should purge contents
          + assertEquals(2, testHarness.numKeyedStateEntries()); // trigger state will stick around
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // gc timers are still there
          +
          + // still no output
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedWindow() throws Exception

          { + testNoTimerFiringForPurgedWindow(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for a non-existent window fires.
          + */
          + private void testNoTimerFiringForPurgedWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }
          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(0, testHarness.numKeyedStateEntries()); // not contents or state
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new EventTimeAdaptor()); + }
          +
          + @Test
          + public void testNoProcessingTimeFiringForPurgedMergingWindow() throws Exception { + testNoTimerFiringForPurgedMergingWindow(new ProcessingTimeAdaptor()); + }
          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * for an empty merging window.
          + */
          + public void testNoTimerFiringForPurgedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't interfere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + return TriggerResult.PURGE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(1, testHarness.numKeyedStateEntries()); // just the merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.advanceTime(testHarness, 0L);
          +
          + // trigger is not called if there is no more window (timer is silently ignored)
          + timeAdaptor.verifyTriggerCallback(mockTrigger, never(), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + assertEquals(1, timeAdaptor.numTimers(testHarness)); // only gc timers left
          + }
          +
          + @Test
          + public void testNoEventTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testNoProcessingTimeFiringForGarbageCollectedMergingWindow() throws Exception

          { + testNoTimerFiringForGarbageCollectedMergingWindow(new ProcessingTimeAdaptor()); + }

          +
          +
          + /**
          + * Verify that we neither invoke the trigger nor the window function if a timer
          + * fires for a merging window that was already garbage collected.
          + */
          + public void testNoTimerFiringForGarbageCollectedMergingWindow(final TimeDomainAdaptor timeAdaptor) throws Exception {
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          +
          + @SuppressWarnings("unchecked")
          + InternalWindowFunction<Iterable<Integer>, List<Integer>, Integer, TimeWindow> mockWindowFunction =
          + mock(InternalWindowFunction.class);
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, List<Integer>> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // set a timer for after the GC time + timeAdaptor.registerTimer(context, 10L); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(2, testHarness.numKeyedStateEntries()); // window contents and merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and gc timer
          +
          + timeAdaptor.shouldContinueOnTime(mockTrigger);
          +
          + // this should trigger GC
          + timeAdaptor.advanceTime(testHarness, 4L);
          +
          + verify(mockTrigger, times(1)).clear(anyTimeWindow(), anyTriggerContext());
          +
          + assertEquals(0, testHarness.numKeyedStateEntries());
          + // we still have a dangling timer because our trigger doesn't do cleanup
          + assertEquals(1, timeAdaptor.numTimers(testHarness));
          +
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          +
          + verify(mockWindowFunction, never())
          + .apply(anyInt(), anyTimeWindow(), anyIntIterable(), EvictingWindowOperatorContractTest.<List<Integer>>anyCollector());
          +
          + // now we trigger the dangling timer
          + timeAdaptor.advanceTime(testHarness, 10L);
          +
          + // we don't fire again
          + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null);
          + }
          +
          + @Test
          + public void testEventTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerCreationAndDeletion() throws Exception

          { + testTimerCreationAndDeletion(new ProcessingTimeAdaptor()); + }

          +
          + private void testTimerCreationAndDeletion(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 2))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeTimerFiring() throws Exception

          { + testTimerFiring(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeTimerFiring() throws Exception

          { + testTimerFiring(new ProcessingTimeAdaptor()); + }

          +
          +
          + private void testTimerFiring(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 17); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 42); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(4, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + timeAdaptor.advanceTime(testHarness, 1); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 1L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + assertEquals(3, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + + // doesn't do anything + timeAdaptor.advanceTime(testHarness, 15); + + // so still the same + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + timeAdaptor.advanceTime(testHarness, 42); + + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 17L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, atLeastOnce(), 42L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(3), null, null); + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 because of the GC timer of the window + }

          +
          + @Test
          + public void testEventTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeDeletedTimerDoesNotFire() throws Exception

          { + testDeletedTimerDoesNotFire(new ProcessingTimeAdaptor()); + }

          +
          + public void testDeletedTimerDoesNotFire(TimeDomainAdaptor timeAdaptor) throws Exception

          { + + WindowAssigner<Integer, TimeWindow> mockAssigner = mockTimeWindowAssigner(); + timeAdaptor.setIsEventTime(mockAssigner); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(0, 100))); + + assertEquals(0, timeAdaptor.numTimers(testHarness)); + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldDeleteTimerOnElement(mockTrigger, 1); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.shouldRegisterTimerOnElement(mockTrigger, 2); + testHarness.processElement(new StreamRecord<>(0, 0L)); + + assertEquals(2, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + + timeAdaptor.advanceTime(testHarness, 50L); + + timeAdaptor.verifyTriggerCallback(mockTrigger, times(0), 1L, null); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), 2L, new TimeWindow(0, 100)); + timeAdaptor.verifyTriggerCallback(mockTrigger, times(1), null, null); + + assertEquals(1, timeAdaptor.numTimers(testHarness)); // +1 for the GC timer + }

          +
          + @Test
          + public void testMergeWindowsIsCalled() throws Exception

          { + + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner(); + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger(); + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction(); + + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness = + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction); + + testHarness.open(); + + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext())) + .thenReturn(Arrays.asList(new TimeWindow(2, 4), new TimeWindow(0, 2))); + + assertEquals(0, testHarness.getOutput().size()); + assertEquals(0, testHarness.numKeyedStateEntries()); + + testHarness.processElement(new StreamRecord<>(0, 0L)); + + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4))), anyMergeCallback()); + verify(mockAssigner).mergeWindows(eq(Lists.newArrayList(new TimeWindow(2, 4), new TimeWindow(0, 2))), anyMergeCallback()); + verify(mockAssigner, times(2)).mergeWindows(anyCollection(), anyMergeCallback()); + + + }

          +
          + @Test
          + public void testEventTimeWindowsAreMergedEagerly() throws Exception

          { + testWindowsAreMergedEagerly(new EventTimeAdaptor()); + }

          +
          + @Test
          + public void testProcessingTimeWindowsAreMergedEagerly() throws Exception

          { + testWindowsAreMergedEagerly(new ProcessingTimeAdaptor()); + }

          +
          + /**
          + * Verify that windows are merged eagerly, if possible.
          + */
          + public void testWindowsAreMergedEagerly(final TimeDomainAdaptor timeAdaptor) throws Exception {
          + // in this test we only have one state window and windows are eagerly
          + // merged into the first window
          +
          + MergingWindowAssigner<Integer, TimeWindow> mockAssigner = mockMergingAssigner();
          + timeAdaptor.setIsEventTime(mockAssigner);
          + Trigger<Integer, TimeWindow> mockTrigger = mockTrigger();
          + InternalWindowFunction<Iterable<Integer>, Void, Integer, TimeWindow> mockWindowFunction = mockWindowFunction();
          +
          + KeyedOneInputStreamOperatorTestHarness<Integer, Integer, Void> testHarness =
          + createWindowOperator(mockAssigner, mockTrigger, 0L, intListDescriptor, mockWindowFunction);
          +
          + testHarness.open();
          +
          + timeAdaptor.advanceTime(testHarness, Long.MIN_VALUE);
          +
          + assertEquals(0, testHarness.extractOutputStreamRecords().size());
          + assertEquals(0, testHarness.numKeyedStateEntries());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[3]; + // don't intefere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onElement(Matchers.<Integer>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext());
          +
          + doAnswer(new Answer<TriggerResult>() {
          + @Override
          + public TriggerResult answer(InvocationOnMock invocation) throws Exception

          { + Trigger.OnMergeContext context = (Trigger.OnMergeContext) invocation.getArguments()[1]; + // don't intefere with cleanup timers + timeAdaptor.registerTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).update("hello"); + return TriggerResult.CONTINUE; + }

          + }).when(mockTrigger).onMerge(anyTimeWindow(), anyOnMergeContext());
          +
          + doAnswer(new Answer<Object>() {
          + @Override
          + public Object answer(InvocationOnMock invocation) throws Exception

          { + Trigger.TriggerContext context = (Trigger.TriggerContext) invocation.getArguments()[1]; + timeAdaptor.deleteTimer(context, 0L); + context.getPartitionedState(valueStateDescriptor).clear(); + return null; + }

          + }).when(mockTrigger).clear(anyTimeWindow(), anyTriggerContext());
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(0, 2)));
          +
          + testHarness.processElement(new StreamRecord<>(0, 0L));
          +
          + assertEquals(3, testHarness.numKeyedStateEntries()); // window state plus trigger state plus merging window set
          + assertEquals(2, timeAdaptor.numTimers(testHarness)); // timer and GC timer
          +
          + when(mockAssigner.assignWindows(anyInt(), anyLong(), anyAssignerContext()))
          + .thenReturn(Arrays.asList(new TimeWindow(2, 4)));
          +
          + shouldMergeWindows(
          + mockAssigner,
          + Lists.newArrayList(new TimeWindow(0, 2), new TimeWindow(2, 4)),
          + Lists.newArrayList(new TimeWindow(0, 2), new TimeWindow(2, 4)),
          — End diff –

          Same here, avoid Guava

          Show
          githubbot ASF GitHub Bot added a comment - Github user tzulitai commented on a diff in the pull request: https://github.com/apache/flink/pull/3587#discussion_r107351397 — Diff: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/operators/windowing/EvictingWindowOperatorContractTest.java — @@ -0,0 +1,2654 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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 org.apache.flink.streaming.runtime.operators.windowing; + + +import static org.apache.flink.streaming.runtime.operators.windowing.StreamRecordMatchers.isStreamRecord; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.*; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.flink.api.common.ExecutionConfig; +import org.apache.flink.api.common.functions.FoldFunction; +import org.apache.flink.api.common.functions.ReduceFunction; +import org.apache.flink.api.common.state.AppendingState; +import org.apache.flink.api.common.state.FoldingStateDescriptor; +import org.apache.flink.api.common.state.ListState; +import org.apache.flink.api.common.state.ListStateDescriptor; +import org.apache.flink.api.common.state.ReducingStateDescriptor; +import org.apache.flink.api.common.state.StateDescriptor; +import org.apache.flink.api.common.state.ValueStateDescriptor; +import org.apache.flink.api.common.typeinfo.BasicTypeInfo; +import org.apache.flink.api.common.typeutils.TypeSerializer; +import org.apache.flink.api.common.typeutils.base.IntSerializer; +import org.apache.flink.api.common.typeutils.base.StringSerializer; +import org.apache.flink.api.java.functions.KeySelector; +import org.apache.flink.streaming.api.watermark.Watermark; +import org.apache.flink.streaming.api.windowing.assigners.MergingWindowAssigner; +import org.apache.flink.streaming.api.windowing.assigners.WindowAssigner; +import org.apache.flink.streaming.api.windowing.evictors.CountEvictor; +import org.apache.flink.streaming.api.windowing.triggers.Trigger; +import org.apache.flink.streaming.api.windowing.triggers.TriggerResult; +import org.apache.flink.streaming.api.windowing.windows.GlobalWindow; +import org.apache.flink.streaming.api.windowing.windows.TimeWindow; +import org.apache.flink.streaming.api.windowing.windows.Window; +import org.apache.flink.streaming.runtime.operators.windowing.functions.InternalWindowFunction; +import org.apache.flink.streaming.runtime.streamrecord.StreamElementSerializer; +import org.apache.flink.streaming.runtime.streamrecord.StreamRecord; +import org.apache.flink.streaming.runtime.tasks.OperatorStateHandles; +import org.apache.flink.streaming.util.AbstractStreamOperatorTestHarness; +import org.apache.flink.streaming.util.KeyedOneInputStreamOperatorTestHarness; +import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness; +import org.apache.flink.util.Collector; +import org.apache.flink.util.OutputTag; +import org.apache.flink.util.TestLogger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.mockito.verification.VerificationMode; + +/** + * These tests verify that {@link WindowOperator} correctly interacts with the other windowing + * components: {@link WindowAssigner} , + * {@link Trigger} . + * {@link org.apache.flink.streaming.api.functions.windowing.WindowFunction} and window state. + * + * <p>These tests document the implicit contract that exists between the windowing components. + * + * <p><b>Important:</b>This test must always be kept up-to-date with + * {@link WindowOperatorContractTest} . + */ +public class EvictingWindowOperatorContractTest extends TestLogger { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private static ValueStateDescriptor<String> valueStateDescriptor = + new ValueStateDescriptor<>("string-state", StringSerializer.INSTANCE, null); + + private static ListStateDescriptor<StreamRecord<Integer>> intListDescriptor = + new ListStateDescriptor<>("int-list", (TypeSerializer<StreamRecord<Integer>>) new StreamElementSerializer(IntSerializer.INSTANCE)); + + static <IN, OUT, KEY, W extends Window> InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction() throws Exception { + @SuppressWarnings("unchecked") + InternalWindowFunction<IN, OUT, KEY, W> mockWindowFunction = mock(InternalWindowFunction.class); + + return mockWindowFunction; + } + + static <T, W extends Window> Trigger<T, W> mockTrigger() throws Exception { + @SuppressWarnings("unchecked") + Trigger<T, W> mockTrigger = mock(Trigger.class); + + when(mockTrigger.onElement(Matchers.<T>any(), anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onEventTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + when(mockTrigger.onProcessingTime(anyLong(), Matchers.<W>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + + return mockTrigger; + } + + static <T> WindowAssigner<T, TimeWindow> mockTimeWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, TimeWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + static <T> WindowAssigner<T, GlobalWindow> mockGlobalWindowAssigner() throws Exception { + @SuppressWarnings("unchecked") + WindowAssigner<T, GlobalWindow> mockAssigner = mock(WindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new GlobalWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + when(mockAssigner.assignWindows(Mockito.<T>any(), anyLong(), anyAssignerContext())).thenReturn(Collections.singletonList(GlobalWindow.get())); + + return mockAssigner; + } + + + static <T> MergingWindowAssigner<T, TimeWindow> mockMergingAssigner() throws Exception { + @SuppressWarnings("unchecked") + MergingWindowAssigner<T, TimeWindow> mockAssigner = mock(MergingWindowAssigner.class); + + when(mockAssigner.getWindowSerializer(Mockito.<ExecutionConfig>any())).thenReturn(new TimeWindow.Serializer()); + when(mockAssigner.isEventTime()).thenReturn(true); + + return mockAssigner; + } + + + static WindowAssigner.WindowAssignerContext anyAssignerContext() { + return Mockito.any(); + } + + static Trigger.TriggerContext anyTriggerContext() { + return Mockito.any(); + } + + static <T> Collector<T> anyCollector() { + return Mockito.any(); + } + + static Iterable<Integer> anyIntIterable() { + return Mockito.any(); + } + + @SuppressWarnings("unchecked") + static Iterable<Integer> intIterable(Integer... values) { + return (Iterable<Integer>) argThat(containsInAnyOrder(values)); + } + + static TimeWindow anyTimeWindow() { + return Mockito.any(); + } + + static Trigger.OnMergeContext anyOnMergeContext() { + return Mockito.any(); + } + + static MergingWindowAssigner.MergeCallback anyMergeCallback() { + return Mockito.any(); + } + + + static <T> void shouldRegisterEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteEventTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteEventTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldRegisterProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.registerProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + private static <T> void shouldDeleteProcessingTimeTimerOnElement(Trigger<T, TimeWindow> mockTrigger, final long timestamp) throws Exception { + doAnswer(new Answer<TriggerResult>() { + @Override + public TriggerResult answer(InvocationOnMock invocation) throws Exception { + @SuppressWarnings("unchecked") + Trigger.TriggerContext context = + (Trigger.TriggerContext) invocation.getArguments()[3]; + context.deleteProcessingTimeTimer(timestamp); + return TriggerResult.CONTINUE; + } + }) + .when(mockTrigger).onElement(Matchers.<T>anyObject(), anyLong(), anyTimeWindow(), anyTriggerContext()); + } + + @SuppressWarnings("unchecked") + private static <T, W extends Window> void shouldMergeWindows(final MergingWindowAssigner<T, W> assigner, final Collection<? extends W> expectedWindows, final Collection<W> toMerge, final W mergeResult) { + doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Exception { + Collection<W> windows = (Collection<W>) invocation.getArguments()[0]; + + MergingWindowAssigner.MergeCallback callback = (MergingWindowAssigner.MergeCallback) invocation.getArguments()[1]; + + // verify the expected windows + assertThat(windows, containsInAnyOrder(expectedWindows.toArray())); + + callback.merge(toMerge, mergeResult); + return null; + } + }) + .when(assigner).mergeWindows(anyCollection(), Matchers.<MergingWindowAssigner.MergeCallback>anyObject()); + } + + private static <T> void shouldContinueOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE); + } + + private static <T> void shouldPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.PURGE); + } + + private static <T> void shouldFireAndPurgeOnElement(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onElement(Matchers.<T>anyObject(), anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.FIRE_AND_PURGE); + } + + private static <T> void shouldContinueOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws Exception { + when(mockTrigger.onEventTime(anyLong(), Matchers.<TimeWindow>any(), anyTriggerContext())).thenReturn(TriggerResult.CONTINUE); + } + + private static <T> void shouldFireOnEventTime(Trigger<T, TimeWindow> mockTrigger) throws