/**
   * This test verifies that open() and close() are correctly called. This test also verifies that
   * timestamps of emitted elements are correct. {@link CoStreamMap} assigns the input timestamp to
   * emitted elements.
   */
  @Test
  @SuppressWarnings("unchecked")
  public void testOpenCloseAndTimestamps() throws Exception {
    final TwoInputStreamTask<String, Integer, String> coMapTask =
        new TwoInputStreamTask<String, Integer, String>();
    final TwoInputStreamTaskTestHarness<String, Integer, String> testHarness =
        new TwoInputStreamTaskTestHarness<String, Integer, String>(
            coMapTask,
            BasicTypeInfo.STRING_TYPE_INFO,
            BasicTypeInfo.INT_TYPE_INFO,
            BasicTypeInfo.STRING_TYPE_INFO);

    StreamConfig streamConfig = testHarness.getStreamConfig();
    CoStreamMap<String, Integer, String> coMapOperator =
        new CoStreamMap<String, Integer, String>(new TestOpenCloseMapFunction());
    streamConfig.setStreamOperator(coMapOperator);

    long initialTime = 0L;
    ConcurrentLinkedQueue<Object> expectedOutput = new ConcurrentLinkedQueue<Object>();

    testHarness.invoke();

    testHarness.processElement(new StreamRecord<String>("Hello", initialTime + 1), 0, 0);
    expectedOutput.add(new StreamRecord<String>("Hello", initialTime + 1));

    // wait until the input is processed to ensure ordering of the output
    testHarness.waitForInputProcessing();

    testHarness.processElement(new StreamRecord<Integer>(1337, initialTime + 2), 1, 0);

    expectedOutput.add(new StreamRecord<String>("1337", initialTime + 2));

    testHarness.endInput();

    testHarness.waitForTaskCompletion();

    Assert.assertTrue(
        "RichFunction methods where not called.", TestOpenCloseMapFunction.closeCalled);

    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());
  }
  /** This test verifies that checkpoint barriers are correctly forwarded. */
  @Test
  @SuppressWarnings("unchecked")
  public void testCheckpointBarriers() throws Exception {
    final TwoInputStreamTask<String, Integer, String> coMapTask =
        new TwoInputStreamTask<String, Integer, String>();
    final TwoInputStreamTaskTestHarness<String, Integer, String> testHarness =
        new TwoInputStreamTaskTestHarness<String, Integer, String>(
            coMapTask,
            2,
            2,
            new int[] {1, 2},
            BasicTypeInfo.STRING_TYPE_INFO,
            BasicTypeInfo.INT_TYPE_INFO,
            BasicTypeInfo.STRING_TYPE_INFO);

    StreamConfig streamConfig = testHarness.getStreamConfig();
    CoStreamMap<String, Integer, String> coMapOperator =
        new CoStreamMap<String, Integer, String>(new IdentityMap());
    streamConfig.setStreamOperator(coMapOperator);

    ConcurrentLinkedQueue<Object> expectedOutput = new ConcurrentLinkedQueue<Object>();
    long initialTime = 0L;

    testHarness.invoke();

    testHarness.processEvent(new CheckpointBarrier(0, 0), 0, 0);

    // This element should be buffered since we received a checkpoint barrier on
    // this input
    testHarness.processElement(new StreamRecord<String>("Hello-0-0", initialTime), 0, 0);

    // This one should go through
    testHarness.processElement(new StreamRecord<String>("Ciao-0-0", initialTime), 0, 1);
    expectedOutput.add(new StreamRecord<String>("Ciao-0-0", initialTime));

    // These elements should be forwarded, since we did not yet receive a checkpoint barrier
    // on that input, only add to same input, otherwise we would not know the ordering
    // of the output since the Task might read the inputs in any order
    testHarness.processElement(new StreamRecord<Integer>(11, initialTime), 1, 1);
    testHarness.processElement(new StreamRecord<Integer>(111, initialTime), 1, 1);
    expectedOutput.add(new StreamRecord<String>("11", initialTime));
    expectedOutput.add(new StreamRecord<String>("111", initialTime));

    testHarness.waitForInputProcessing();
    // we should not yet see the barrier, only the two elements from non-blocked input
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", testHarness.getOutput(), expectedOutput);

    testHarness.processEvent(new CheckpointBarrier(0, 0), 0, 1);
    testHarness.processEvent(new CheckpointBarrier(0, 0), 1, 0);
    testHarness.processEvent(new CheckpointBarrier(0, 0), 1, 1);

    testHarness.waitForInputProcessing();

    // now we should see the barrier and after that the buffered elements
    expectedOutput.add(new CheckpointBarrier(0, 0));
    expectedOutput.add(new StreamRecord<String>("Hello-0-0", initialTime));
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", testHarness.getOutput(), expectedOutput);

    testHarness.endInput();

    testHarness.waitForTaskCompletion();

    List<String> resultElements = TestHarnessUtil.getRawElementsFromOutput(testHarness.getOutput());
    Assert.assertEquals(4, resultElements.size());
  }
  /**
   * This test verifies that checkpoint barriers and barrier buffers work correctly with concurrent
   * checkpoint barriers where one checkpoint is "overtaking" another checkpoint, i.e. some inputs
   * receive barriers from an earlier checkpoint, thereby blocking, then all inputs receive barriers
   * from a later checkpoint.
   */
  @Test
  @SuppressWarnings("unchecked")
  public void testOvertakingCheckpointBarriers() throws Exception {
    final TwoInputStreamTask<String, Integer, String> coMapTask =
        new TwoInputStreamTask<String, Integer, String>();
    final TwoInputStreamTaskTestHarness<String, Integer, String> testHarness =
        new TwoInputStreamTaskTestHarness<String, Integer, String>(
            coMapTask,
            2,
            2,
            new int[] {1, 2},
            BasicTypeInfo.STRING_TYPE_INFO,
            BasicTypeInfo.INT_TYPE_INFO,
            BasicTypeInfo.STRING_TYPE_INFO);

    StreamConfig streamConfig = testHarness.getStreamConfig();
    CoStreamMap<String, Integer, String> coMapOperator =
        new CoStreamMap<String, Integer, String>(new IdentityMap());
    streamConfig.setStreamOperator(coMapOperator);

    ConcurrentLinkedQueue<Object> expectedOutput = new ConcurrentLinkedQueue<Object>();
    long initialTime = 0L;

    testHarness.invoke();

    testHarness.processEvent(new CheckpointBarrier(0, 0), 0, 0);

    // These elements should be buffered until we receive barriers from
    // all inputs
    testHarness.processElement(new StreamRecord<String>("Hello-0-0", initialTime), 0, 0);
    testHarness.processElement(new StreamRecord<String>("Ciao-0-0", initialTime), 0, 0);

    // These elements should be forwarded, since we did not yet receive a checkpoint barrier
    // on that input, only add to same input, otherwise we would not know the ordering
    // of the output since the Task might read the inputs in any order
    testHarness.processElement(new StreamRecord<Integer>(42, initialTime), 1, 1);
    testHarness.processElement(new StreamRecord<Integer>(1337, initialTime), 1, 1);
    expectedOutput.add(new StreamRecord<String>("42", initialTime));
    expectedOutput.add(new StreamRecord<String>("1337", initialTime));

    testHarness.waitForInputProcessing();
    // we should not yet see the barrier, only the two elements from non-blocked input
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    // Now give a later barrier to all inputs, this should unblock the first channel,
    // thereby allowing the two blocked elements through
    testHarness.processEvent(new CheckpointBarrier(1, 1), 0, 0);
    testHarness.processEvent(new CheckpointBarrier(1, 1), 0, 1);
    testHarness.processEvent(new CheckpointBarrier(1, 1), 1, 0);
    testHarness.processEvent(new CheckpointBarrier(1, 1), 1, 1);

    expectedOutput.add(new StreamRecord<String>("Hello-0-0", initialTime));
    expectedOutput.add(new StreamRecord<String>("Ciao-0-0", initialTime));
    expectedOutput.add(new CheckpointBarrier(1, 1));

    testHarness.waitForInputProcessing();

    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    // Then give the earlier barrier, these should be ignored
    testHarness.processEvent(new CheckpointBarrier(0, 0), 0, 1);
    testHarness.processEvent(new CheckpointBarrier(0, 0), 1, 0);
    testHarness.processEvent(new CheckpointBarrier(0, 0), 1, 1);

    testHarness.waitForInputProcessing();

    testHarness.endInput();

    testHarness.waitForTaskCompletion();

    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());
  }
  /**
   * This test verifies that watermarks are correctly forwarded. This also checks whether watermarks
   * are forwarded only when we have received watermarks from all inputs. The forwarded watermark
   * must be the minimum of the watermarks of all inputs.
   */
  @Test
  @SuppressWarnings("unchecked")
  public void testWatermarkForwarding() throws Exception {
    final TwoInputStreamTask<String, Integer, String> coMapTask =
        new TwoInputStreamTask<String, Integer, String>();
    final TwoInputStreamTaskTestHarness<String, Integer, String> testHarness =
        new TwoInputStreamTaskTestHarness<String, Integer, String>(
            coMapTask,
            2,
            2,
            new int[] {1, 2},
            BasicTypeInfo.STRING_TYPE_INFO,
            BasicTypeInfo.INT_TYPE_INFO,
            BasicTypeInfo.STRING_TYPE_INFO);

    StreamConfig streamConfig = testHarness.getStreamConfig();
    CoStreamMap<String, Integer, String> coMapOperator =
        new CoStreamMap<String, Integer, String>(new IdentityMap());
    streamConfig.setStreamOperator(coMapOperator);

    ConcurrentLinkedQueue<Object> expectedOutput = new ConcurrentLinkedQueue<Object>();
    long initialTime = 0L;

    testHarness.invoke();

    testHarness.processElement(new Watermark(initialTime), 0, 0);
    testHarness.processElement(new Watermark(initialTime), 0, 1);

    testHarness.processElement(new Watermark(initialTime), 1, 0);

    // now the output should still be empty
    testHarness.waitForInputProcessing();
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    testHarness.processElement(new Watermark(initialTime), 1, 1);

    // now the watermark should have propagated, Map simply forward Watermarks
    testHarness.waitForInputProcessing();
    expectedOutput.add(new Watermark(initialTime));
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    // contrary to checkpoint barriers these elements are not blocked by watermarks
    testHarness.processElement(new StreamRecord<String>("Hello", initialTime), 0, 0);
    testHarness.processElement(new StreamRecord<Integer>(42, initialTime), 1, 1);
    expectedOutput.add(new StreamRecord<String>("Hello", initialTime));
    expectedOutput.add(new StreamRecord<String>("42", initialTime));

    testHarness.waitForInputProcessing();
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    testHarness.processElement(new Watermark(initialTime + 4), 0, 0);
    testHarness.processElement(new Watermark(initialTime + 3), 0, 1);
    testHarness.processElement(new Watermark(initialTime + 3), 1, 0);
    testHarness.processElement(new Watermark(initialTime + 2), 1, 1);

    // check whether we get the minimum of all the watermarks, this must also only occur in
    // the output after the two StreamRecords
    expectedOutput.add(new Watermark(initialTime + 2));
    testHarness.waitForInputProcessing();
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    // advance watermark from one of the inputs, now we should get a now one since the
    // minimum increases
    testHarness.processElement(new Watermark(initialTime + 4), 1, 1);
    testHarness.waitForInputProcessing();
    expectedOutput.add(new Watermark(initialTime + 3));
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    // advance the other two inputs, now we should get a new one since the
    // minimum increases again
    testHarness.processElement(new Watermark(initialTime + 4), 0, 1);
    testHarness.processElement(new Watermark(initialTime + 4), 1, 0);
    testHarness.waitForInputProcessing();
    expectedOutput.add(new Watermark(initialTime + 4));
    TestHarnessUtil.assertOutputEquals(
        "Output was not correct.", expectedOutput, testHarness.getOutput());

    testHarness.endInput();

    testHarness.waitForTaskCompletion();

    List<String> resultElements = TestHarnessUtil.getRawElementsFromOutput(testHarness.getOutput());
    Assert.assertEquals(2, resultElements.size());
  }