/* GStreamer unit tests for multiqueue
 *
 * Copyright (C) 2007 Tim-Philipp Müller <tim centricular net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/check/gstcheck.h>

static GMutex _check_lock;

static GstElement *
setup_multiqueue (GstElement * pipe, GstElement * inputs[],
    GstElement * outputs[], guint num)
{
  GstElement *mq;
  guint i;

  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL, "failed to create 'multiqueue' element");

  gst_bin_add (GST_BIN (pipe), mq);

  for (i = 0; i < num; ++i) {
    GstPad *sinkpad = NULL;
    GstPad *srcpad = NULL;

    /* create multiqueue sink (and source) pad */
    sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
    fail_unless (sinkpad != NULL,
        "failed to create multiqueue request pad #%u", i);

    /* link input element N to the N-th multiqueue sink pad we just created */
    if (inputs != NULL && inputs[i] != NULL) {
      gst_bin_add (GST_BIN (pipe), inputs[i]);

      srcpad = gst_element_get_static_pad (inputs[i], "src");
      fail_unless (srcpad != NULL, "failed to find src pad for input #%u", i);

      fail_unless_equals_int (GST_PAD_LINK_OK, gst_pad_link (srcpad, sinkpad));

      gst_object_unref (srcpad);
      srcpad = NULL;
    }
    gst_object_unref (sinkpad);
    sinkpad = NULL;

    /* link output element N to the N-th multiqueue src pad */
    if (outputs != NULL && outputs[i] != NULL) {
      gchar padname[10];

      /* only the sink pads are by request, the source pads are sometimes pads,
       * so this should return NULL */
      srcpad = gst_element_request_pad_simple (mq, "src_%u");
      fail_unless (srcpad == NULL);

      g_snprintf (padname, sizeof (padname), "src_%u", i);
      srcpad = gst_element_get_static_pad (mq, padname);
      fail_unless (srcpad != NULL, "failed to get multiqueue src pad #%u", i);
      fail_unless (GST_PAD_IS_SRC (srcpad),
          "%s:%s is not a source pad?!", GST_DEBUG_PAD_NAME (srcpad));

      gst_bin_add (GST_BIN (pipe), outputs[i]);

      sinkpad = gst_element_get_static_pad (outputs[i], "sink");
      fail_unless (sinkpad != NULL, "failed to find sink pad of output #%u", i);
      fail_unless (GST_PAD_IS_SINK (sinkpad));

      fail_unless_equals_int (GST_PAD_LINK_OK, gst_pad_link (srcpad, sinkpad));

      gst_object_unref (srcpad);
      gst_object_unref (sinkpad);
    }
  }

  return mq;
}

GST_START_TEST (test_simple_pipeline)
{
  GstElement *pipe;
  GstElement *inputs[1];
  GstElement *outputs[1];
  GstMessage *msg;

  pipe = gst_pipeline_new ("pipeline");

  inputs[0] = gst_element_factory_make ("fakesrc", NULL);
  fail_unless (inputs[0] != NULL, "failed to create 'fakesrc' element");
  g_object_set (inputs[0], "num-buffers", 256, NULL);

  outputs[0] = gst_element_factory_make ("fakesink", NULL);
  fail_unless (outputs[0] != NULL, "failed to create 'fakesink' element");

  setup_multiqueue (pipe, inputs, outputs, 1);

  gst_element_set_state (pipe, GST_STATE_PLAYING);

  msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
      GST_MESSAGE_EOS | GST_MESSAGE_ERROR, -1);

  fail_if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR,
      "Expected EOS message, got ERROR message");
  gst_message_unref (msg);

  GST_LOG ("Got EOS, cleaning up");

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);
}

GST_END_TEST;

GST_START_TEST (test_simple_shutdown_while_running)
{
  GstElement *pipe;
  GstElement *inputs[1];
  GstElement *outputs[1];
  GstMessage *msg;

  pipe = gst_pipeline_new ("pipeline");

  inputs[0] = gst_element_factory_make ("fakesrc", NULL);
  fail_unless (inputs[0] != NULL, "failed to create 'fakesrc' element");

  outputs[0] = gst_element_factory_make ("fakesink", NULL);
  fail_unless (outputs[0] != NULL, "failed to create 'fakesink' element");

  setup_multiqueue (pipe, inputs, outputs, 1);

  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* wait until pipeline is up and running */
  msg = gst_bus_poll (GST_ELEMENT_BUS (pipe),
      GST_MESSAGE_ERROR | GST_MESSAGE_ASYNC_DONE, -1);
  fail_if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR, "Got ERROR message");
  gst_message_unref (msg);

  GST_LOG ("pipeline is running now");
  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* wait a bit to accumulate some buffers in the queue (while it's blocking
   * in the sink) */
  msg =
      gst_bus_poll (GST_ELEMENT_BUS (pipe), GST_MESSAGE_ERROR, GST_SECOND / 4);
  if (msg)
    g_error ("Got ERROR message");

  /* now shut down only the sink, so the queue gets a wrong-state flow return */
  gst_element_set_state (outputs[0], GST_STATE_NULL);
  msg =
      gst_bus_poll (GST_ELEMENT_BUS (pipe), GST_MESSAGE_ERROR, GST_SECOND / 2);
  if (msg)
    g_error ("Got ERROR message");

  GST_LOG ("Cleaning up");

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);
}

GST_END_TEST;

GST_START_TEST (test_simple_create_destroy)
{
  GstElement *mq;

  mq = gst_element_factory_make ("multiqueue", NULL);
  gst_object_unref (mq);
}

GST_END_TEST;

GST_START_TEST (test_request_pads)
{
  GstElement *mq;
  GstPad *sink1, *sink2;

  mq = gst_element_factory_make ("multiqueue", NULL);

  sink1 = gst_element_request_pad_simple (mq, "foo_%u");
  fail_unless (sink1 == NULL,
      "Expected NULL pad, as there is no request pad template for 'foo_%%u'");

  sink1 = gst_element_request_pad_simple (mq, "src_%u");
  fail_unless (sink1 == NULL,
      "Expected NULL pad, as there is no request pad template for 'src_%%u'");

  sink1 = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (sink1 != NULL);
  fail_unless (GST_IS_PAD (sink1));
  fail_unless (GST_PAD_IS_SINK (sink1));
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink1));

  sink2 = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (sink2 != NULL);
  fail_unless (GST_IS_PAD (sink2));
  fail_unless (GST_PAD_IS_SINK (sink2));
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink2));

  fail_unless (sink1 != sink2);

  GST_LOG ("Cleaning up");
  gst_object_unref (sink1);
  gst_object_unref (sink2);
  gst_object_unref (mq);
}

GST_END_TEST;

static GstPad *
mq_sinkpad_to_srcpad (GstElement * mq, GstPad * sink)
{
  GstPad *srcpad = NULL;

  gchar *mq_sinkpad_name;
  gchar *mq_srcpad_name;

  mq_sinkpad_name = gst_pad_get_name (sink);
  fail_unless (g_str_has_prefix (mq_sinkpad_name, "sink"));
  mq_srcpad_name = g_strdup_printf ("src_%s", mq_sinkpad_name + 5);
  srcpad = gst_element_get_static_pad (mq, mq_srcpad_name);
  fail_unless (srcpad != NULL);

  g_free (mq_sinkpad_name);
  g_free (mq_srcpad_name);

  return srcpad;
}

GST_START_TEST (test_request_pads_named)
{
  GstElement *mq;
  GstPad *sink1, *sink2, *sink3, *sink4;

  mq = gst_element_factory_make ("multiqueue", NULL);

  sink1 = gst_element_request_pad_simple (mq, "sink_1");
  fail_unless (sink1 != NULL);
  fail_unless (GST_IS_PAD (sink1));
  fail_unless (GST_PAD_IS_SINK (sink1));
  fail_unless_equals_string (GST_PAD_NAME (sink1), "sink_1");
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink1));

  sink3 = gst_element_request_pad_simple (mq, "sink_3");
  fail_unless (sink3 != NULL);
  fail_unless (GST_IS_PAD (sink3));
  fail_unless (GST_PAD_IS_SINK (sink3));
  fail_unless_equals_string (GST_PAD_NAME (sink3), "sink_3");
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink3));

  sink2 = gst_element_request_pad_simple (mq, "sink_2");
  fail_unless (sink2 != NULL);
  fail_unless (GST_IS_PAD (sink2));
  fail_unless (GST_PAD_IS_SINK (sink2));
  fail_unless_equals_string (GST_PAD_NAME (sink2), "sink_2");
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink2));

  /* This gets us the first unused id, sink0 */
  sink4 = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (sink4 != NULL);
  fail_unless (GST_IS_PAD (sink4));
  fail_unless (GST_PAD_IS_SINK (sink4));
  fail_unless_equals_string (GST_PAD_NAME (sink4), "sink_0");
  GST_LOG ("Got pad %s:%s", GST_DEBUG_PAD_NAME (sink4));

  GST_LOG ("Cleaning up");
  gst_object_unref (sink1);
  gst_object_unref (sink2);
  gst_object_unref (sink3);
  gst_object_unref (sink4);
  gst_object_unref (mq);
}

GST_END_TEST;

static gboolean
mq_dummypad_query (GstPad * sinkpad, GstObject * parent, GstQuery * query)
{
  gboolean res = TRUE;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CAPS:
    {
      GstCaps *filter, *caps;

      gst_query_parse_caps (query, &filter);
      caps = (filter ? gst_caps_ref (filter) : gst_caps_new_any ());
      gst_query_set_caps_result (query, caps);
      gst_caps_unref (caps);
      break;
    }
    default:
      res = gst_pad_query_default (sinkpad, parent, query);
      break;
  }
  return res;
}

struct PadData
{
  GstPad *input_pad;
  GstPad *out_pad;
  guint8 pad_num;
  guint32 *max_linked_id_ptr;
  guint32 *eos_count_ptr;
  gboolean is_linked;
  gboolean first_buf;
  gint n_linked;

  GMutex *mutex;
  GCond *cond;

  /* used by initial_events_nodelay */
  gint event_count;
};

static GstFlowReturn
mq_dummypad_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buf)
{
  guint32 cur_id;
  struct PadData *pad_data;
  GstMapInfo info;

  pad_data = gst_pad_get_element_private (sinkpad);

  g_mutex_lock (&_check_lock);
  fail_if (pad_data == NULL);
  /* Read an ID from the first 4 bytes of the buffer data and check it's
   * what we expect */
  fail_unless (gst_buffer_map (buf, &info, GST_MAP_READ));
  fail_unless (info.size >= 4);
  g_mutex_unlock (&_check_lock);
  cur_id = GST_READ_UINT32_BE (info.data);
  gst_buffer_unmap (buf, &info);

  g_mutex_lock (pad_data->mutex);

  /* For not-linked pads, ensure that we're not running ahead of the 'linked'
   * pads. The first buffer is allowed to get ahead, because otherwise things can't
   * always pre-roll correctly */
  if (pad_data->max_linked_id_ptr) {
    if (!pad_data->is_linked) {
      /* If there are no linked pads, we can't track a max_id for them :) */
      if (pad_data->n_linked > 0 && !pad_data->first_buf) {
        g_mutex_lock (&_check_lock);
        fail_unless (cur_id <= *(pad_data->max_linked_id_ptr) + 1,
            "Got buffer %u on pad %u before buffer %u was seen on a "
            "linked pad (max: %u)", cur_id, pad_data->pad_num, cur_id - 1,
            *(pad_data->max_linked_id_ptr));
        g_mutex_unlock (&_check_lock);
      }
    } else {
      /* Update the max_id value */
      if (cur_id > *(pad_data->max_linked_id_ptr))
        *(pad_data->max_linked_id_ptr) = cur_id;
    }
  }
  pad_data->first_buf = FALSE;

  g_mutex_unlock (pad_data->mutex);

  /* Unref the buffer */
  gst_buffer_unref (buf);

  /* Return OK or not-linked as indicated */
  return pad_data->is_linked ? GST_FLOW_OK : GST_FLOW_NOT_LINKED;
}

static gboolean
mq_dummypad_event (GstPad * sinkpad, GstObject * parent, GstEvent * event)
{
  struct PadData *pad_data;

  pad_data = gst_pad_get_element_private (sinkpad);
  g_mutex_lock (&_check_lock);
  fail_if (pad_data == NULL);
  g_mutex_unlock (&_check_lock);

  if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
    g_mutex_lock (pad_data->mutex);

    /* Accumulate that we've seen the EOS and signal the main thread */
    if (pad_data->eos_count_ptr)
      *(pad_data->eos_count_ptr) += 1;

    GST_DEBUG ("EOS on pad %u", pad_data->pad_num);

    g_cond_broadcast (pad_data->cond);
    g_mutex_unlock (pad_data->mutex);
  }

  gst_event_unref (event);
  return TRUE;
}

static void
construct_n_pads (GstElement * mq, struct PadData *pad_data, gint n_pads,
    gint n_linked)
{
  gint i;
  GstSegment segment;

  gst_segment_init (&segment, GST_FORMAT_BYTES);

  /* Construct NPADS dummy output pads. The first 'n_linked' return FLOW_OK, the rest
   * return NOT_LINKED. The not-linked ones check the expected ordering of 
   * output buffers */
  for (i = 0; i < n_pads; i++) {
    GstPad *mq_srcpad, *mq_sinkpad, *inpad, *outpad;
    gchar *name;

    name = g_strdup_printf ("dummysrc%d", i);
    inpad = gst_pad_new (name, GST_PAD_SRC);
    g_free (name);
    gst_pad_set_query_function (inpad, mq_dummypad_query);

    mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
    fail_unless (mq_sinkpad != NULL);
    fail_unless (gst_pad_link (inpad, mq_sinkpad) == GST_PAD_LINK_OK);

    gst_pad_set_active (inpad, TRUE);

    gst_pad_push_event (inpad, gst_event_new_stream_start ("test"));
    gst_pad_push_event (inpad, gst_event_new_segment (&segment));

    mq_srcpad = mq_sinkpad_to_srcpad (mq, mq_sinkpad);

    name = g_strdup_printf ("dummysink%d", i);
    outpad = gst_pad_new (name, GST_PAD_SINK);
    g_free (name);
    gst_pad_set_chain_function (outpad, mq_dummypad_chain);
    gst_pad_set_event_function (outpad, mq_dummypad_event);
    gst_pad_set_query_function (outpad, mq_dummypad_query);

    pad_data[i].pad_num = i;
    pad_data[i].input_pad = inpad;
    pad_data[i].out_pad = outpad;
    pad_data[i].max_linked_id_ptr = NULL;
    pad_data[i].eos_count_ptr = NULL;
    pad_data[i].is_linked = (i < n_linked ? TRUE : FALSE);
    pad_data[i].n_linked = n_linked;
    pad_data[i].cond = NULL;
    pad_data[i].mutex = NULL;
    pad_data[i].first_buf = TRUE;
    gst_pad_set_element_private (outpad, pad_data + i);

    fail_unless (gst_pad_link (mq_srcpad, outpad) == GST_PAD_LINK_OK);
    gst_pad_set_active (outpad, TRUE);

    gst_object_unref (mq_sinkpad);
    gst_object_unref (mq_srcpad);
  }
}

static void
push_n_buffers (struct PadData *pad_data, gint num_buffers,
    const guint8 * pad_pattern, guint pattern_size)
{
  gint i;

  for (i = 0; i < num_buffers; i++) {
    guint8 cur_pad;
    GstBuffer *buf;
    GstFlowReturn ret;
    GstMapInfo info;

    cur_pad = pad_pattern[i % pattern_size];

    buf = gst_buffer_new_and_alloc (4);
    g_mutex_lock (&_check_lock);
    fail_if (buf == NULL);
    g_mutex_unlock (&_check_lock);

    fail_unless (gst_buffer_map (buf, &info, GST_MAP_WRITE));
    GST_WRITE_UINT32_BE (info.data, i + 1);
    gst_buffer_unmap (buf, &info);
    GST_BUFFER_TIMESTAMP (buf) = (i + 1) * GST_SECOND;

    ret = gst_pad_push (pad_data[cur_pad].input_pad, buf);
    g_mutex_lock (&_check_lock);
    if (pad_data[cur_pad].is_linked) {
      fail_unless (ret == GST_FLOW_OK,
          "Push on pad %d returned %d when FLOW_OK was expected", cur_pad, ret);
    } else {
      /* Expect OK initially, then NOT_LINKED when the srcpad starts pushing */
      fail_unless (ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED,
          "Push on pad %d returned %d when FLOW_OK or NOT_LINKED  was expected",
          cur_pad, ret);
    }
    g_mutex_unlock (&_check_lock);
  }
}

static void
run_output_order_test (gint n_linked)
{
  /* This test creates a multiqueue with 2 linked output, and 3 outputs that
   * return 'not-linked' when data is pushed, then verifies that all buffers
   * are received on not-linked pads only after earlier buffers on the
   * 'linked' pads are made */
  GstElement *pipe;
  GstElement *mq;
  struct PadData pad_data[5];
  guint32 max_linked_id;
  guint32 eos_seen;
  GMutex mutex;
  GCond cond;
  gint i;
  const gint NPADS = 5;
  const gint NBUFFERS = 1000;

  g_mutex_init (&mutex);
  g_cond_init (&cond);

  pipe = gst_bin_new ("testbin");

  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  /* No limits */
  g_object_set (mq,
      "max-size-bytes", (guint) 0,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0, "extra-size-time", (guint64) 0, NULL);

  construct_n_pads (mq, pad_data, NPADS, n_linked);
  for (i = 0; i < NPADS; i++) {
    pad_data[i].max_linked_id_ptr = &max_linked_id;
    /* Only look for EOS on the linked pads */
    pad_data[i].eos_count_ptr = (i < n_linked) ? &eos_seen : NULL;
    pad_data[i].cond = &cond;
    pad_data[i].mutex = &mutex;
  }

  /* Run the test. Push 1000 buffers through the multiqueue in a pattern */
  max_linked_id = 0;
  eos_seen = 0;
  gst_element_set_state (pipe, GST_STATE_PLAYING);

  {
    const guint8 pad_pattern[] =
        { 0, 0, 0, 0, 1, 1, 2, 1, 0, 2, 3, 2, 3, 1, 4 };
    const guint n = sizeof (pad_pattern) / sizeof (guint8);
    push_n_buffers (pad_data, NBUFFERS, pad_pattern, n);
  }

  for (i = 0; i < NPADS; i++) {
    gst_pad_push_event (pad_data[i].input_pad, gst_event_new_eos ());
  }

  /* Wait while the buffers are processed */
  g_mutex_lock (&mutex);
  /* We wait until EOS has been pushed on all linked pads */
  while (eos_seen < n_linked) {
    g_cond_wait (&cond, &mutex);
  }
  g_mutex_unlock (&mutex);

  /* Clean up */
  for (i = 0; i < NPADS; i++) {
    GstPad *mq_input = gst_pad_get_peer (pad_data[i].input_pad);

    gst_pad_unlink (pad_data[i].input_pad, mq_input);
    gst_element_release_request_pad (mq, mq_input);
    gst_object_unref (mq_input);
    gst_object_unref (pad_data[i].input_pad);
    gst_object_unref (pad_data[i].out_pad);
  }

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);

  g_cond_clear (&cond);
  g_mutex_clear (&mutex);
}

GST_START_TEST (test_output_order)
{
  run_output_order_test (2);
  run_output_order_test (0);
}

GST_END_TEST;

GST_START_TEST (test_not_linked_eos)
{
  /* This test creates a multiqueue with 1 linked output and 1 not-linked
   * pad. It pushes a few buffers through each, then EOS on the linked
   * pad and waits until that arrives. After that, it pushes some more
   * buffers on the not-linked pad and then EOS and checks that those
   * are all output */
  GstElement *pipe;
  GstElement *mq;
  struct PadData pad_data[2];
  guint32 eos_seen;
  GMutex mutex;
  GCond cond;
  gint i;
  const gint NPADS = 2;
  const gint NBUFFERS = 20;
  GstSegment segment;

  gst_segment_init (&segment, GST_FORMAT_BYTES);

  g_mutex_init (&mutex);
  g_cond_init (&cond);

  pipe = gst_bin_new ("testbin");

  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  /* No limits */
  g_object_set (mq,
      "max-size-bytes", (guint) 0,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0, "extra-size-time", (guint64) 0, NULL);

  /* Construct NPADS dummy output pads. The first 1 returns FLOW_OK, the rest
   * return NOT_LINKED. */
  construct_n_pads (mq, pad_data, NPADS, 1);
  for (i = 0; i < NPADS; i++) {
    /* Only look for EOS on the linked pads */
    pad_data[i].eos_count_ptr = &eos_seen;
    pad_data[i].cond = &cond;
    pad_data[i].mutex = &mutex;
  }

  /* Run the test. Push 20 buffers through the multiqueue in a pattern */
  eos_seen = 0;
  gst_element_set_state (pipe, GST_STATE_PLAYING);

  {
    const guint8 pad_pattern[] = { 0, 1 };
    const guint n = sizeof (pad_pattern) / sizeof (guint8);
    push_n_buffers (pad_data, NBUFFERS, pad_pattern, n);
  }

  /* Make the linked pad go EOS */
  gst_pad_push_event (pad_data[0].input_pad, gst_event_new_eos ());

  g_mutex_lock (&mutex);
  /* Wait until EOS has been seen on the linked pad */
  while (eos_seen == 0)
    g_cond_wait (&cond, &mutex);
  g_mutex_unlock (&mutex);

  /* Now push some more buffers to the not-linked pad */
  {
    const guint8 pad_pattern[] = { 1, 1 };
    const guint n = sizeof (pad_pattern) / sizeof (guint8);
    push_n_buffers (pad_data, NBUFFERS, pad_pattern, n);
  }
  /* And EOS on the not-linked pad */
  gst_pad_push_event (pad_data[1].input_pad, gst_event_new_eos ());

  g_mutex_lock (&mutex);
  while (eos_seen < NPADS)
    g_cond_wait (&cond, &mutex);
  g_mutex_unlock (&mutex);

  /* Clean up */
  for (i = 0; i < NPADS; i++) {
    GstPad *mq_input = gst_pad_get_peer (pad_data[i].input_pad);

    gst_pad_unlink (pad_data[i].input_pad, mq_input);
    gst_element_release_request_pad (mq, mq_input);
    gst_object_unref (mq_input);
    gst_object_unref (pad_data[i].input_pad);
    gst_object_unref (pad_data[i].out_pad);
  }

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);

  g_cond_clear (&cond);
  g_mutex_clear (&mutex);
}

GST_END_TEST;

GST_START_TEST (test_sparse_stream)
{
  /* This test creates a multiqueue with 2 streams. One receives
   * a constant flow of buffers, the other only gets one buffer, and then
   * new-segment events, and returns not-linked. The multiqueue should not fill.
   */
  GstElement *pipe;
  GstElement *mq;
  GstPad *inputpads[2];
  GstPad *sinkpads[2];
  GstEvent *event;
  struct PadData pad_data[2];
  guint32 eos_seen, max_linked_id;
  GMutex mutex;
  GCond cond;
  gint i;
  const gint NBUFFERS = 100;
  GstSegment segment;

  g_mutex_init (&mutex);
  g_cond_init (&cond);

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  /* 1 second limit */
  g_object_set (mq,
      "max-size-bytes", (guint) 0,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) GST_SECOND,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0, "extra-size-time", (guint64) 0, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  /* Construct 2 dummy output pads. */
  for (i = 0; i < 2; i++) {
    GstPad *mq_srcpad, *mq_sinkpad;
    gchar *name;

    name = g_strdup_printf ("dummysrc%d", i);
    inputpads[i] = gst_pad_new (name, GST_PAD_SRC);
    g_free (name);
    gst_pad_set_query_function (inputpads[i], mq_dummypad_query);

    mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
    fail_unless (mq_sinkpad != NULL);
    fail_unless (gst_pad_link (inputpads[i], mq_sinkpad) == GST_PAD_LINK_OK);

    gst_pad_set_active (inputpads[i], TRUE);

    gst_pad_push_event (inputpads[i], gst_event_new_stream_start ("test"));
    gst_pad_push_event (inputpads[i], gst_event_new_segment (&segment));

    mq_srcpad = mq_sinkpad_to_srcpad (mq, mq_sinkpad);

    name = g_strdup_printf ("dummysink%d", i);
    sinkpads[i] = gst_pad_new (name, GST_PAD_SINK);
    g_free (name);
    gst_pad_set_chain_function (sinkpads[i], mq_dummypad_chain);
    gst_pad_set_event_function (sinkpads[i], mq_dummypad_event);
    gst_pad_set_query_function (sinkpads[i], mq_dummypad_query);

    pad_data[i].pad_num = i;
    pad_data[i].max_linked_id_ptr = &max_linked_id;
    if (i == 0)
      pad_data[i].eos_count_ptr = &eos_seen;
    else
      pad_data[i].eos_count_ptr = NULL;
    pad_data[i].is_linked = (i == 0) ? TRUE : FALSE;
    pad_data[i].n_linked = 1;
    pad_data[i].cond = &cond;
    pad_data[i].mutex = &mutex;
    pad_data[i].first_buf = TRUE;
    gst_pad_set_element_private (sinkpads[i], pad_data + i);

    fail_unless (gst_pad_link (mq_srcpad, sinkpads[i]) == GST_PAD_LINK_OK);
    gst_pad_set_active (sinkpads[i], TRUE);

    gst_object_unref (mq_sinkpad);
    gst_object_unref (mq_srcpad);
  }

  /* Run the test. Push 100 buffers through the multiqueue */
  max_linked_id = 0;
  eos_seen = 0;

  gst_element_set_state (pipe, GST_STATE_PLAYING);

  for (i = 0; i < NBUFFERS; i++) {
    GstBuffer *buf;
    GstFlowReturn ret;
    GstClockTime ts;
    GstMapInfo info;

    ts = gst_util_uint64_scale_int (GST_SECOND, i, 10);

    buf = gst_buffer_new_and_alloc (4);
    g_mutex_lock (&_check_lock);
    fail_if (buf == NULL);
    g_mutex_unlock (&_check_lock);

    fail_unless (gst_buffer_map (buf, &info, GST_MAP_WRITE));
    GST_WRITE_UINT32_BE (info.data, i + 1);
    gst_buffer_unmap (buf, &info);

    GST_BUFFER_TIMESTAMP (buf) = gst_util_uint64_scale_int (GST_SECOND, i, 10);

    /* If i == 0, also push the buffer to the 2nd pad */
    if (i == 0)
      ret = gst_pad_push (inputpads[1], gst_buffer_ref (buf));

    ret = gst_pad_push (inputpads[0], buf);
    g_mutex_lock (&_check_lock);
    fail_unless (ret == GST_FLOW_OK,
        "Push on pad %d returned %d when FLOW_OK was expected", 0, ret);
    g_mutex_unlock (&_check_lock);

    /* Push a new segment update on the 2nd pad */
    gst_segment_init (&segment, GST_FORMAT_TIME);
    segment.start = ts;
    segment.time = ts;
    event = gst_event_new_segment (&segment);
    gst_pad_push_event (inputpads[1], event);
  }

  event = gst_event_new_eos ();
  gst_pad_push_event (inputpads[0], gst_event_ref (event));
  gst_pad_push_event (inputpads[1], event);

  /* Wait while the buffers are processed */
  g_mutex_lock (&mutex);
  /* We wait until EOS has been pushed on pad 1 */
  while (eos_seen < 1) {
    g_cond_wait (&cond, &mutex);
  }
  g_mutex_unlock (&mutex);

  /* Clean up */
  for (i = 0; i < 2; i++) {
    GstPad *mq_input = gst_pad_get_peer (inputpads[i]);

    gst_pad_unlink (inputpads[i], mq_input);
    gst_element_release_request_pad (mq, mq_input);
    gst_object_unref (mq_input);
    gst_object_unref (inputpads[i]);

    gst_object_unref (sinkpads[i]);
  }

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);

  g_cond_clear (&cond);
  g_mutex_clear (&mutex);
}

GST_END_TEST;

static gpointer
pad_push_datablock_thread (gpointer data)
{
  GstPad *pad = data;
  GstBuffer *buf;

  buf = gst_buffer_new_allocate (NULL, 80 * 1000, NULL);
  gst_pad_push (pad, buf);

  return NULL;
}

static GstPadProbeReturn
block_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  return GST_PAD_PROBE_OK;
}

static void
check_for_buffering_msg (GstElement * pipeline, gint expected_perc)
{
  gint buf_perc;
  GstMessage *msg;

  GST_LOG ("waiting for %d%% buffering message", expected_perc);

  msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline),
      GST_MESSAGE_BUFFERING | GST_MESSAGE_ERROR, -1);
  fail_if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR,
      "Expected BUFFERING message, got ERROR message");

  gst_message_parse_buffering (msg, &buf_perc);
  fail_unless (buf_perc == expected_perc,
      "Got incorrect percentage: %d%% expected: %d%%", buf_perc, expected_perc);

  gst_message_unref (msg);
}

GST_START_TEST (test_initial_fill_above_high_threshold)
{
  /* This test checks what happens if the first buffer that enters
   * the queue immediately fills it above the high-threshold. */
  GstElement *pipe;
  GstElement *mq, *fakesink;
  GstPad *inputpad;
  GstPad *mq_sinkpad;
  GstPad *sinkpad;
  GstSegment segment;
  GThread *thread;


  /* Setup test pipeline with one multiqueue and one fakesink */

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  fakesink = gst_element_factory_make ("fakesink", NULL);
  fail_unless (fakesink != NULL);
  gst_bin_add (GST_BIN (pipe), fakesink);

  /* Block fakesink sinkpad flow to ensure the queue isn't emptied
   * by the prerolling sink */
  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BLOCK, block_probe, NULL,
      NULL);
  gst_object_unref (sinkpad);

  /* Set size limit to 1000000 byte, low threshold to 1%, high
   * threshold to 5%, to make sure that even just one data push
   * will exceed both thresholds.*/
  g_object_set (mq,
      "use-buffering", (gboolean) TRUE,
      "max-size-bytes", (guint) 1000 * 1000,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0,
      "extra-size-time", (guint64) 0,
      "low-percent", (gint) 1, "high-percent", (gint) 5, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  gst_pad_set_query_function (inputpad, mq_dummypad_query);

  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_object_unref (mq_sinkpad);

  fail_unless (gst_element_link (mq, fakesink));

  /* Start pipeline in paused state to ensure the sink remains
   * in preroll mode and blocks */
  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* Feed data. queue will be filled to 8% (because it pushes 80000 bytes),
   * which is above both the low- and the high-threshold. This should
   * produce a 100% buffering message. */
  thread = g_thread_new ("push1", pad_push_datablock_thread, inputpad);
  g_thread_join (thread);
  check_for_buffering_msg (pipe, 100);

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (pipe);
}

GST_END_TEST;

GST_START_TEST (test_watermark_and_fill_level)
{
  /* This test checks the behavior of the fill level and
   * the low/high watermarks. It also checks if the
   * low/high-percent and low/high-watermark properties
   * are coupled together properly. */
  GstElement *pipe;
  GstElement *mq, *fakesink;
  GstPad *inputpad;
  GstPad *mq_sinkpad;
  GstPad *sinkpad;
  GstSegment segment;
  GThread *thread;
  gint low_perc, high_perc;


  /* Setup test pipeline with one multiqueue and one fakesink */

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  fakesink = gst_element_factory_make ("fakesink", NULL);
  fail_unless (fakesink != NULL);
  gst_bin_add (GST_BIN (pipe), fakesink);

  /* Block fakesink sinkpad flow to ensure the queue isn't emptied
   * by the prerolling sink */
  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BLOCK, block_probe, NULL,
      NULL);
  gst_object_unref (sinkpad);

  g_object_set (mq,
      "use-buffering", (gboolean) TRUE,
      "max-size-bytes", (guint) 1000 * 1000,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0,
      "extra-size-time", (guint64) 0,
      "low-watermark", (gdouble) 0.01, "high-watermark", (gdouble) 0.10, NULL);

  g_object_get (mq, "low-percent", &low_perc, "high-percent", &high_perc, NULL);

  /* Check that low/high-watermark and low/high-percent are
   * coupled properly. (low/high-percent are deprecated and
   * exist for backwards compatibility.) */
  fail_unless_equals_int (low_perc, 1);
  fail_unless_equals_int (high_perc, 10);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  gst_pad_set_query_function (inputpad, mq_dummypad_query);

  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_object_unref (mq_sinkpad);

  fail_unless (gst_element_link (mq, fakesink));

  /* Start pipeline in paused state to ensure the sink remains
   * in preroll mode and blocks */
  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* Feed data. queue will be filled to 8% (because it pushes 80000 bytes),
   * which is below the high-threshold, provoking a buffering message. */
  thread = g_thread_new ("push1", pad_push_datablock_thread, inputpad);
  g_thread_join (thread);

  /* Check for the buffering message; it should indicate 80% fill level
   * (Note that the percentage from the message is normalized) */
  check_for_buffering_msg (pipe, 80);

  /* Increase the buffer size and lower the watermarks to test
   * if <1% watermarks are supported. */
  g_object_set (mq,
      "max-size-bytes", (guint) 20 * 1000 * 1000,
      "low-watermark", (gdouble) 0.0001, "high-watermark", (gdouble) 0.005,
      NULL);
  /* First buffering message is posted after the max-size-bytes limit
   * is set to 20000000 bytes & the low-watermark is set. Since the
   * multiqueue contains 80000 bytes, and the high watermark still is
   * 0.1 at this point, and the buffer level 80000 / 20000000 = 0.004 is
   * normalized by 0.1: 0.004 / 0.1 => buffering percentage 4%. */
  check_for_buffering_msg (pipe, 4);
  /* Second buffering message is posted after the high-watermark limit
   * is set to 0.005. This time, the buffer level is normalized this way:
   * 0.004 / 0.005 => buffering percentage 80%. */
  check_for_buffering_msg (pipe, 80);


  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (pipe);
}

GST_END_TEST;

GST_START_TEST (test_high_threshold_change)
{
  /* This test checks what happens if the high threshold is changed to a
   * value below the current buffer fill level. Expected behavior is for
   * multiqueue to emit a 100% buffering message in that case. */
  GstElement *pipe;
  GstElement *mq, *fakesink;
  GstPad *inputpad;
  GstPad *mq_sinkpad;
  GstPad *sinkpad;
  GstSegment segment;
  GThread *thread;


  /* Setup test pipeline with one multiqueue and one fakesink */

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  fakesink = gst_element_factory_make ("fakesink", NULL);
  fail_unless (fakesink != NULL);
  gst_bin_add (GST_BIN (pipe), fakesink);

  /* Block fakesink sinkpad flow to ensure the queue isn't emptied
   * by the prerolling sink */
  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BLOCK, block_probe, NULL,
      NULL);
  gst_object_unref (sinkpad);

  g_object_set (mq,
      "use-buffering", (gboolean) TRUE,
      "max-size-bytes", (guint) 1000 * 1000,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0,
      "extra-size-time", (guint64) 0,
      "low-percent", (gint) 1, "high-percent", (gint) 99, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  gst_pad_set_query_function (inputpad, mq_dummypad_query);

  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_object_unref (mq_sinkpad);

  fail_unless (gst_element_link (mq, fakesink));

  /* Start pipeline in paused state to ensure the sink remains
   * in preroll mode and blocks */
  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* Feed data. queue will be filled to 8% (because it pushes 80000 bytes),
   * which is below the high-threshold, provoking a buffering message. */
  thread = g_thread_new ("push1", pad_push_datablock_thread, inputpad);
  g_thread_join (thread);

  /* Check for the buffering message; it should indicate 8% fill level
   * (Note that the percentage from the message is normalized, but since
   * the high threshold is at 99%, it should still apply) */
  check_for_buffering_msg (pipe, 8);

  /* Set high threshold to half of what it was before. This means that the
   * relative fill level doubles. As a result, this should trigger a buffering
   * message with a percentage of 16%. */
  g_object_set (mq, "high-percent", (gint) 50, NULL);
  check_for_buffering_msg (pipe, 16);

  /* Set high threshold to a value that lies below the current fill level.
   * This should trigger a 100% buffering message immediately, even without
   * pushing in extra data. */
  g_object_set (mq, "high-percent", (gint) 5, NULL);
  check_for_buffering_msg (pipe, 100);

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (pipe);
}

GST_END_TEST;

GST_START_TEST (test_low_threshold_change)
{
  /* This tests what happens if the queue isn't currently buffering and the
   * low-threshold is raised above the current fill level. */
  GstElement *pipe;
  GstElement *mq, *fakesink;
  GstPad *inputpad;
  GstPad *mq_sinkpad;
  GstPad *sinkpad;
  GstSegment segment;
  GThread *thread;


  /* Setup test pipeline with one multiqueue and one fakesink */

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  fakesink = gst_element_factory_make ("fakesink", NULL);
  fail_unless (fakesink != NULL);
  gst_bin_add (GST_BIN (pipe), fakesink);

  /* Block fakesink sinkpad flow to ensure the queue isn't emptied
   * by the prerolling sink */
  sinkpad = gst_element_get_static_pad (fakesink, "sink");
  gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_BLOCK, block_probe, NULL,
      NULL);
  gst_object_unref (sinkpad);

  /* Enable buffering and set the low/high thresholds to 1%/5%. This ensures
   * that after pushing one data block, the high threshold is reached, and
   * buffering ceases. */
  g_object_set (mq,
      "use-buffering", (gboolean) TRUE,
      "max-size-bytes", (guint) 1000 * 1000,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0,
      "extra-size-time", (guint64) 0,
      "low-percent", (gint) 1, "high-percent", (gint) 5, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  gst_pad_set_query_function (inputpad, mq_dummypad_query);

  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_object_unref (mq_sinkpad);

  fail_unless (gst_element_link (mq, fakesink));

  /* Start pipeline in paused state to ensure the sink remains
   * in preroll mode and blocks */
  gst_element_set_state (pipe, GST_STATE_PAUSED);

  /* Feed data. queue will be filled to 8% (because it pushes 80000 bytes),
   * which is above the high-threshold, ensuring that the queue disables
   * its buffering mode internally. */
  thread = g_thread_new ("push1", pad_push_datablock_thread, inputpad);
  g_thread_join (thread);

  /* Check for the buffering message; it should indicate 100% relative fill
   * level (Note that the percentage from the message is normalized) */
  check_for_buffering_msg (pipe, 100);

  /* Set low threshold to a 10%, which is above the current fill level of 8%.
   * As a result, the queue must re-enable its buffering mode, and post the
   * current relative fill level of 40% (since high-percent is also set to 20%
   * and 8%/20% = 40%). */
  g_object_set (mq, "high-percent", (gint) 20, "low-percent", (gint) 10, NULL);
  check_for_buffering_msg (pipe, 40);

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (pipe);
}

GST_END_TEST;

static gpointer
pad_push_thread (gpointer data)
{
  GstPad *pad = data;
  GstBuffer *buf;

  buf = gst_buffer_new ();
  gst_pad_push (pad, buf);

  return NULL;
}

GST_START_TEST (test_limit_changes)
{
  /* This test creates a multiqueue with 1 stream. The limit of the queue
   * is two buffers, we check if we block once this is reached. Then we
   * change the limit to three buffers and check if this is waking up
   * the queue and we get the third buffer.
   */
  GstElement *pipe;
  GstElement *mq, *fakesink;
  GstPad *inputpad;
  GstPad *mq_sinkpad;
  GstSegment segment;
  GThread *thread;

  pipe = gst_pipeline_new ("testbin");
  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  fakesink = gst_element_factory_make ("fakesink", NULL);
  fail_unless (fakesink != NULL);
  gst_bin_add (GST_BIN (pipe), fakesink);

  g_object_set (mq,
      "max-size-bytes", (guint) 0,
      "max-size-buffers", (guint) 2,
      "max-size-time", (guint64) 0,
      "extra-size-bytes", (guint) 0,
      "extra-size-buffers", (guint) 0, "extra-size-time", (guint64) 0, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  gst_pad_set_query_function (inputpad, mq_dummypad_query);

  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_object_unref (mq_sinkpad);

  fail_unless (gst_element_link (mq, fakesink));

  gst_element_set_state (pipe, GST_STATE_PAUSED);

  thread = g_thread_new ("push1", pad_push_thread, inputpad);
  g_thread_join (thread);
  thread = g_thread_new ("push2", pad_push_thread, inputpad);
  g_thread_join (thread);
  thread = g_thread_new ("push3", pad_push_thread, inputpad);
  g_thread_join (thread);
  thread = g_thread_new ("push4", pad_push_thread, inputpad);

  /* Wait until we are actually blocking... we unfortunately can't
   * know that without sleeping */
  g_usleep (G_USEC_PER_SEC);
  g_object_set (mq, "max-size-buffers", (guint) 3, NULL);
  g_thread_join (thread);

  g_object_set (mq, "max-size-buffers", (guint) 4, NULL);
  thread = g_thread_new ("push5", pad_push_thread, inputpad);
  g_thread_join (thread);

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (pipe);
}

GST_END_TEST;

static GMutex block_mutex;
static GCond block_cond;
static gint unblock_count;
static gboolean expect_overrun;

static GstFlowReturn
pad_chain_block (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
  g_mutex_lock (&block_mutex);
  while (unblock_count == 0) {
    g_cond_wait (&block_cond, &block_mutex);
  }
  if (unblock_count > 0) {
    unblock_count--;
  }
  g_mutex_unlock (&block_mutex);

  gst_buffer_unref (buffer);
  return GST_FLOW_OK;
}

static gboolean
pad_event_always_ok (GstPad * pad, GstObject * parent, GstEvent * event)
{
  gst_event_unref (event);
  return TRUE;
}

static void
mq_overrun (GstElement * mq, gpointer udata)
{
  fail_unless (expect_overrun);

  /* unblock always so we don't get stuck */
  g_mutex_lock (&block_mutex);
  unblock_count = 2;            /* let the PTS=0 and PTS=none go */
  g_cond_signal (&block_cond);
  g_mutex_unlock (&block_mutex);
}

GST_START_TEST (test_buffering_with_none_pts)
{
  /*
   * This test creates a multiqueue where source pushing blocks so we can check
   * how its buffering level is reacting to GST_CLOCK_TIME_NONE buffers
   * mixed with properly timestamped buffers.
   *
   * Sequence of pushing:
   * pts=0
   * pts=none
   * pts=1 (it gets full now)
   * pts=none (overrun expected)
   */
  GstElement *mq;
  GstPad *inputpad;
  GstPad *outputpad;
  GstPad *mq_sinkpad;
  GstPad *mq_srcpad;
  GstSegment segment;
  GstBuffer *buffer;

  g_mutex_init (&block_mutex);
  g_cond_init (&block_cond);
  unblock_count = 0;
  expect_overrun = FALSE;

  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);

  g_object_set (mq,
      "max-size-bytes", (guint) 0,
      "max-size-buffers", (guint) 0,
      "max-size-time", (guint64) GST_SECOND, NULL);
  g_signal_connect (mq, "overrun", (GCallback) mq_overrun, NULL);

  gst_segment_init (&segment, GST_FORMAT_TIME);

  inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);
  outputpad = gst_pad_new ("dummysink", GST_PAD_SINK);
  gst_pad_set_chain_function (outputpad, pad_chain_block);
  gst_pad_set_event_function (outputpad, pad_event_always_ok);
  mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
  mq_srcpad = gst_element_get_static_pad (mq, "src_0");
  fail_unless (mq_sinkpad != NULL);
  fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);
  fail_unless (gst_pad_link (mq_srcpad, outputpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (inputpad, TRUE);
  gst_pad_set_active (outputpad, TRUE);
  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  gst_element_set_state (mq, GST_STATE_PAUSED);

  /* push a buffer with PTS = 0 */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = 0;
  fail_unless (gst_pad_push (inputpad, buffer) == GST_FLOW_OK);

  /* push a buffer with PTS = NONE */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
  fail_unless (gst_pad_push (inputpad, buffer) == GST_FLOW_OK);

  /* push a buffer with PTS = 1s, so we have 1s of data in multiqueue, we are
   * full */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = GST_SECOND;
  fail_unless (gst_pad_push (inputpad, buffer) == GST_FLOW_OK);

  /* push a buffer with PTS = NONE, the queue is full so it should overrun */
  expect_overrun = TRUE;
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
  fail_unless (gst_pad_push (inputpad, buffer) == GST_FLOW_OK);

  g_mutex_lock (&block_mutex);
  unblock_count = -1;
  g_cond_signal (&block_cond);
  g_mutex_unlock (&block_mutex);

  gst_element_set_state (mq, GST_STATE_NULL);
  gst_object_unref (inputpad);
  gst_object_unref (outputpad);
  gst_object_unref (mq_sinkpad);
  gst_object_unref (mq_srcpad);
  gst_object_unref (mq);
  g_mutex_clear (&block_mutex);
  g_cond_clear (&block_cond);
}

GST_END_TEST;

static gboolean
event_func_signal (GstPad * sinkpad, GstObject * parent, GstEvent * event)
{
  struct PadData *pad_data;

  GST_LOG_OBJECT (sinkpad, "%s event", GST_EVENT_TYPE_NAME (event));

  pad_data = gst_pad_get_element_private (sinkpad);

  g_mutex_lock (pad_data->mutex);
  ++pad_data->event_count;
  g_cond_broadcast (pad_data->cond);
  g_mutex_unlock (pad_data->mutex);

  gst_event_unref (event);
  return TRUE;
}

GST_START_TEST (test_initial_events_nodelay)
{
  struct PadData pad_data = { 0, };
  GstElement *pipe;
  GstElement *mq;
  GstPad *inputpad;
  GstPad *sinkpad;
  GstSegment segment;
  GstCaps *caps;
  GMutex mutex;
  GCond cond;

  g_mutex_init (&mutex);
  g_cond_init (&cond);

  pipe = gst_pipeline_new ("testbin");

  mq = gst_element_factory_make ("multiqueue", NULL);
  fail_unless (mq != NULL);
  gst_bin_add (GST_BIN (pipe), mq);

  {
    GstPad *mq_srcpad, *mq_sinkpad;

    inputpad = gst_pad_new ("dummysrc", GST_PAD_SRC);

    mq_sinkpad = gst_element_request_pad_simple (mq, "sink_%u");
    fail_unless (mq_sinkpad != NULL);
    fail_unless (gst_pad_link (inputpad, mq_sinkpad) == GST_PAD_LINK_OK);

    gst_pad_set_active (inputpad, TRUE);

    mq_srcpad = mq_sinkpad_to_srcpad (mq, mq_sinkpad);

    sinkpad = gst_pad_new ("dummysink", GST_PAD_SINK);
    gst_pad_set_event_function (sinkpad, event_func_signal);

    pad_data.event_count = 0;
    pad_data.cond = &cond;
    pad_data.mutex = &mutex;
    gst_pad_set_element_private (sinkpad, &pad_data);

    fail_unless (gst_pad_link (mq_srcpad, sinkpad) == GST_PAD_LINK_OK);
    gst_pad_set_active (sinkpad, TRUE);

    gst_object_unref (mq_sinkpad);
    gst_object_unref (mq_srcpad);
  }

  /* Run the test: push events through multiqueue */
  gst_element_set_state (pipe, GST_STATE_PLAYING);

  gst_pad_push_event (inputpad, gst_event_new_stream_start ("test"));

  caps = gst_caps_new_empty_simple ("foo/x-bar");
  gst_pad_push_event (inputpad, gst_event_new_caps (caps));
  gst_caps_unref (caps);

  gst_segment_init (&segment, GST_FORMAT_TIME);
  gst_pad_push_event (inputpad, gst_event_new_segment (&segment));

  g_mutex_lock (&mutex);
  while (pad_data.event_count < 3) {
    GST_LOG ("%d events so far, waiting for more", pad_data.event_count);
    g_cond_wait (&cond, &mutex);
  }
  g_mutex_unlock (&mutex);

  /* Clean up */
  {
    GstPad *mq_input = gst_pad_get_peer (inputpad);

    gst_pad_unlink (inputpad, mq_input);
    gst_element_release_request_pad (mq, mq_input);
    gst_object_unref (mq_input);
    gst_object_unref (inputpad);

    gst_object_unref (sinkpad);
  }

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);

  g_cond_clear (&cond);
  g_mutex_clear (&mutex);
}

GST_END_TEST;

static void
check_for_stream_status_msg (GstElement * pipeline, GstElement * multiqueue,
    GstStreamStatusType expected_type)
{
  GEnumClass *klass;
  const gchar *expected_nick, *nick;
  GstMessage *msg;
  GstStreamStatusType type;
  GstElement *owner;

  klass = g_type_class_ref (GST_TYPE_STREAM_STATUS_TYPE);
  expected_nick = g_enum_get_value (klass, expected_type)->value_nick;

  GST_LOG ("waiting for stream-status %s message", expected_nick);

  msg = gst_bus_poll (GST_ELEMENT_BUS (pipeline),
      GST_MESSAGE_STREAM_STATUS | GST_MESSAGE_ERROR, -1);
  fail_if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR,
      "Expected stream-status message, got error message");

  gst_message_parse_stream_status (msg, &type, &owner);
  nick = g_enum_get_value (klass, type)->value_nick;
  fail_unless (owner == multiqueue,
      "Got incorrect owner: %" GST_PTR_FORMAT " expected: %" GST_PTR_FORMAT,
      owner, multiqueue);
  fail_unless (type == expected_type,
      "Got incorrect type: %s expected: %s", nick, expected_nick);

  gst_message_unref (msg);
  g_type_class_unref (klass);
}

GST_START_TEST (test_stream_status_messages)
{
  GstElement *pipe, *mq;
  GstPad *pad;

  pipe = gst_pipeline_new ("pipeline");
  mq = gst_element_factory_make ("multiqueue", NULL);

  gst_bin_add (GST_BIN (pipe), mq);

  pad = gst_element_request_pad_simple (mq, "sink_%u");
  gst_object_unref (pad);

  gst_element_set_state (pipe, GST_STATE_PAUSED);

  check_for_stream_status_msg (pipe, mq, GST_STREAM_STATUS_TYPE_CREATE);
  check_for_stream_status_msg (pipe, mq, GST_STREAM_STATUS_TYPE_ENTER);

  pad = gst_element_request_pad_simple (mq, "sink_%u");
  gst_object_unref (pad);

  check_for_stream_status_msg (pipe, mq, GST_STREAM_STATUS_TYPE_CREATE);
  check_for_stream_status_msg (pipe, mq, GST_STREAM_STATUS_TYPE_ENTER);

  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);
}

GST_END_TEST;

static Suite *
multiqueue_suite (void)
{
  Suite *s = suite_create ("multiqueue");
  TCase *tc_chain = tcase_create ("general");

  suite_add_tcase (s, tc_chain);
  tcase_add_test (tc_chain, test_simple_create_destroy);
  tcase_add_test (tc_chain, test_simple_pipeline);
  tcase_add_test (tc_chain, test_simple_shutdown_while_running);

  tcase_add_test (tc_chain, test_request_pads);
  tcase_add_test (tc_chain, test_request_pads_named);

  /* Disabled, The test (and not multiqueue itself) is racy.
   * See https://bugzilla.gnome.org/show_bug.cgi?id=708661 */
  tcase_skip_broken_test (tc_chain, test_output_order);

  tcase_add_test (tc_chain, test_not_linked_eos);

  tcase_add_test (tc_chain, test_sparse_stream);
  tcase_add_test (tc_chain, test_initial_fill_above_high_threshold);
  tcase_add_test (tc_chain, test_watermark_and_fill_level);
  tcase_add_test (tc_chain, test_high_threshold_change);
  tcase_add_test (tc_chain, test_low_threshold_change);
  tcase_add_test (tc_chain, test_limit_changes);

  tcase_add_test (tc_chain, test_buffering_with_none_pts);
  tcase_add_test (tc_chain, test_initial_events_nodelay);

  tcase_add_test (tc_chain, test_stream_status_messages);

  return s;
}

GST_CHECK_MAIN (multiqueue)
