/* GStreamer
 * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
 *
 * gstpad.c: Unit test for GstPad
 *
 * 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 GstSegment dummy_segment;

GST_START_TEST (test_link)
{
  GstPad *src, *sink;
  GstPadTemplate *srct;

  GstPadLinkReturn ret;
  gchar *name;

  src = gst_pad_new ("source", GST_PAD_SRC);
  fail_if (src == NULL);
  ASSERT_OBJECT_REFCOUNT (src, "source pad", 1);

  name = gst_pad_get_name (src);
  fail_unless (strcmp (name, "source") == 0);
  ASSERT_OBJECT_REFCOUNT (src, "source pad", 1);
  g_free (name);

  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);

  /* linking without templates or caps should work */
  ret = gst_pad_link (src, sink);
  ASSERT_OBJECT_REFCOUNT (src, "source pad", 1);
  ASSERT_OBJECT_REFCOUNT (sink, "sink pad", 1);
  fail_unless (ret == GST_PAD_LINK_OK);

  ASSERT_CRITICAL (gst_pad_get_pad_template (NULL));

  srct = gst_pad_get_pad_template (src);
  fail_unless (srct == NULL);
  ASSERT_OBJECT_REFCOUNT (src, "source pad", 1);

  /* clean up */
  ASSERT_OBJECT_REFCOUNT (src, "source pad", 1);
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

/* threaded link/unlink */
/* use globals */
static GstPad *src, *sink;

static void
thread_link_unlink (gpointer data)
{
  THREAD_START ();

  while (THREAD_TEST_RUNNING ()) {
    gst_pad_link (src, sink);
    gst_pad_unlink (src, sink);
    THREAD_SWITCH ();
  }
}

GST_START_TEST (test_link_unlink_threaded)
{
  GstCaps *caps;
  int i;

  src = gst_pad_new ("source", GST_PAD_SRC);
  fail_if (src == NULL);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);

  caps = gst_caps_from_string ("foo/bar");
  gst_pad_set_active (src, TRUE);
  gst_pad_set_caps (src, caps);
  gst_pad_set_active (sink, TRUE);
  gst_pad_set_caps (sink, caps);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);

  MAIN_START_THREADS (5, thread_link_unlink, NULL);
  for (i = 0; i < 1000; ++i) {
    gst_pad_is_linked (src);
    gst_pad_is_linked (sink);
    THREAD_SWITCH ();
  }
  MAIN_STOP_THREADS ();

  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);
  gst_caps_unref (caps);

  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

GST_START_TEST (test_refcount)
{
  GstPad *src, *sink;
  GstCaps *caps;
  GstPadLinkReturn plr;

  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);

  caps = gst_caps_from_string ("foo/bar");
  /* one for me */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  /* can't set caps on flushing sinkpad */
  fail_if (gst_pad_set_caps (src, caps) == TRUE);
  fail_if (gst_pad_set_caps (sink, caps) == TRUE);
  /* one for me and one for each set_caps */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  gst_pad_set_active (src, TRUE);
  fail_unless (gst_pad_set_caps (src, caps) == TRUE);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  gst_pad_set_active (sink, TRUE);
  fail_unless (gst_pad_set_caps (sink, caps) == TRUE);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));
  /* src caps added to pending caps on sink */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);

  gst_pad_unlink (src, sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);

  /* cleanup */
  gst_object_unref (src);
  gst_object_unref (sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  gst_caps_unref (caps);
}

GST_END_TEST;

GST_START_TEST (test_get_allowed_caps)
{
  GstPad *src, *sink;
  GstCaps *caps, *gotcaps;
  GstPadLinkReturn plr;

  ASSERT_CRITICAL (gst_pad_get_allowed_caps (NULL));

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);
  caps = gst_pad_get_allowed_caps (src);
  fail_unless (caps == NULL);

  caps = gst_caps_from_string ("foo/bar");

  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_active (src, TRUE);
  /* source pad is active and will accept the caps event */
  fail_unless (gst_pad_set_caps (src, caps) == TRUE);
  /* sink pad is not active and will refuse the caps event */
  fail_if (gst_pad_set_caps (sink, caps) == TRUE);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  gst_pad_set_active (sink, TRUE);
  /* sink pad is now active and will accept the caps event */
  fail_unless (gst_pad_set_caps (sink, caps) == TRUE);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));

  gotcaps = gst_pad_get_allowed_caps (src);
  fail_if (gotcaps == NULL);
  fail_unless (gst_caps_is_equal (gotcaps, caps));

  ASSERT_CAPS_REFCOUNT (gotcaps, "gotcaps", 4);
  gst_caps_unref (gotcaps);

  gst_pad_unlink (src, sink);

  /* cleanup */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 3);
  ASSERT_OBJECT_REFCOUNT (src, "src", 1);
  ASSERT_OBJECT_REFCOUNT (sink, "sink", 1);

  gst_object_unref (src);
  gst_object_unref (sink);

  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  gst_caps_unref (caps);
}

GST_END_TEST;

static GstCaps *event_caps = NULL;

static gboolean
sticky_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
  GstCaps *caps;

  fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_CAPS
      || GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START
      || GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT);

  if (GST_EVENT_TYPE (event) != GST_EVENT_CAPS) {
    gst_event_unref (event);
    return TRUE;
  }

  /* Ensure we get here just once: */
  fail_unless (event_caps == NULL);

  /* The event must arrive before any buffer: */
  fail_unless_equals_int (g_list_length (buffers), 0);

  gst_event_parse_caps (event, &caps);
  event_caps = gst_caps_ref (caps);

  gst_event_unref (event);

  return TRUE;
}

/* Tests whether caps get properly forwarded when pads
   are initially unlinked */
GST_START_TEST (test_sticky_caps_unlinked)
{
  GstCaps *caps;
  GstPadTemplate *src_template, *sink_template;
  GstPad *src, *sink;
  GstEvent *event;

  caps = gst_caps_from_string ("foo/bar, dummy=(int){1, 2}");
  src_template = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, caps);
  sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, caps);
  gst_caps_unref (caps);

  src = gst_pad_new_from_template (src_template, "src");
  fail_if (src == NULL);
  sink = gst_pad_new_from_template (sink_template, "sink");
  fail_if (sink == NULL);
  gst_pad_set_event_function (sink, sticky_event);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  gst_object_unref (src_template);
  gst_object_unref (sink_template);

  gst_pad_set_active (src, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);

  caps = gst_caps_from_string ("foo/bar, dummy=(int)1");
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  event = gst_event_new_caps (caps);
  fail_unless (gst_pad_push_event (src, event) == TRUE);
  fail_unless (event_caps == NULL);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* Linking and activating will not forward the sticky event yet... */
  fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (src, sink)));
  gst_pad_set_active (sink, TRUE);
  fail_unless (event_caps == NULL);

  /* ...but the first buffer will: */
  fail_unless (gst_pad_push (src, gst_buffer_new ()) == GST_FLOW_OK);
  fail_unless (event_caps == caps);
  fail_unless_equals_int (g_list_length (buffers), 1);

  gst_check_drop_buffers ();

  gst_caps_replace (&caps, NULL);
  gst_caps_replace (&event_caps, NULL);
  ASSERT_OBJECT_REFCOUNT (src, "src", 1);
  ASSERT_OBJECT_REFCOUNT (sink, "sink", 1);
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static gboolean
check_if_caps_is_accepted (GstPad * sink, const gchar * str)
{
  GstCaps *caps;
  gboolean ret;

  caps = gst_caps_from_string (str);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  ret = gst_pad_query_accept_caps (sink, caps);
  gst_caps_unref (caps);

  return ret;
}

static gboolean
sink_query_caps (GstPad * pad, GstObject * object, GstQuery * q)
{
  gboolean ret;
  GstCaps *caps;

  switch (GST_QUERY_TYPE (q)) {
    case GST_QUERY_CAPS:
      ret = TRUE;
      caps =
          gst_caps_from_string ("foo/bar, dummy=(int)1,"
          " query-only-field=(int)1");
      gst_query_set_caps_result (q, caps);
      gst_caps_unref (caps);
    default:
      ret = gst_pad_query_default (pad, object, q);
      break;
  }

  return ret;
}

/* Tests whether acceptcaps default handler works properly
   with all 4 possible flag combinations */
GST_START_TEST (test_default_accept_caps)
{
  GstCaps *caps;
  GstPadTemplate *sink_template;
  GstPad *sink;

  caps = gst_caps_from_string ("foo/bar, dummy=(int){1, 2}");
  sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, caps);
  gst_caps_unref (caps);

  sink = gst_pad_new_from_template (sink_template, "sink");
  fail_if (sink == NULL);
  gst_pad_set_query_function (sink, sink_query_caps);

  gst_object_unref (sink_template);

  gst_pad_set_active (sink, TRUE);

  /* 1. Check with caps query, subset check */
  GST_PAD_UNSET_ACCEPT_INTERSECT (sink);
  GST_PAD_UNSET_ACCEPT_TEMPLATE (sink);
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)3"));
  fail_unless (check_if_caps_is_accepted (sink,
          "foo/bar, dummy=(int)1, query-only-field=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, extra-field=(int)1"));

  /* 2. Check with caps query, intersect check */
  GST_PAD_SET_ACCEPT_INTERSECT (sink);
  GST_PAD_UNSET_ACCEPT_TEMPLATE (sink);
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)3"));
  fail_unless (check_if_caps_is_accepted (sink,
          "foo/bar, dummy=(int)1, query-only-field=(int)1"));
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, extra-field=(int)1"));

  /* 3. Check with template caps, subset check */
  GST_PAD_UNSET_ACCEPT_INTERSECT (sink);
  GST_PAD_SET_ACCEPT_TEMPLATE (sink);
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)3"));
  fail_unless (check_if_caps_is_accepted (sink,
          "foo/bar, dummy=(int)1, query-only-field=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, extra-field=(int)1"));

  /* 3. Check with template caps, intersect check */
  GST_PAD_SET_ACCEPT_INTERSECT (sink);
  GST_PAD_SET_ACCEPT_TEMPLATE (sink);
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)1"));
  fail_if (check_if_caps_is_accepted (sink, "foo/bar, dummy=(int)3"));
  fail_unless (check_if_caps_is_accepted (sink,
          "foo/bar, dummy=(int)1, query-only-field=(int)1"));
  fail_unless (check_if_caps_is_accepted (sink, "foo/bar, extra-field=(int)1"));

  ASSERT_OBJECT_REFCOUNT (sink, "sink", 1);
  gst_object_unref (sink);
}

GST_END_TEST;

/* Same as test_sticky_caps_unlinked except that the source pad
 * has a template of ANY and we will attempt to push
 * incompatible caps */
GST_START_TEST (test_sticky_caps_unlinked_incompatible)
{
  GstCaps *caps, *failcaps;
  GstPadTemplate *src_template, *sink_template;
  GstPad *src, *sink;
  GstEvent *event;

  /* Source pad has ANY caps
   * Sink pad has foobar caps
   * We will push the pony express caps (which should fail)
   */
  caps = gst_caps_new_any ();
  src_template = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, caps);
  gst_caps_unref (caps);
  caps = gst_caps_from_string ("foo/bar, dummy=(int){1, 2}");
  sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, caps);
  gst_caps_unref (caps);

  src = gst_pad_new_from_template (src_template, "src");
  fail_if (src == NULL);
  sink = gst_pad_new_from_template (sink_template, "sink");
  fail_if (sink == NULL);
  gst_pad_set_event_function (sink, sticky_event);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  gst_object_unref (src_template);
  gst_object_unref (sink_template);

  gst_pad_set_active (src, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);

  failcaps = gst_caps_from_string ("pony/express, failure=(boolean)true");
  ASSERT_CAPS_REFCOUNT (failcaps, "caps", 1);

  event = gst_event_new_caps (failcaps);
  gst_caps_unref (failcaps);
  /* The pad isn't linked yet, and anything matches the source pad template
   * (which is ANY) */
  fail_unless (gst_pad_push_event (src, event) == TRUE);
  fail_unless (event_caps == NULL);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* Linking and activating will not forward the sticky event yet... */
  fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (src, sink)));
  gst_pad_set_active (sink, TRUE);
  fail_unless (event_caps == NULL);

  /* ...but the first buffer will and should FAIL since the caps 
   * are not compatible */
  fail_unless (gst_pad_push (src,
          gst_buffer_new ()) == GST_FLOW_NOT_NEGOTIATED);
  /* We shouldn't have received the caps event since it's incompatible */
  fail_unless (event_caps == NULL);
  /* We shouldn't have received any buffers since caps are incompatible */
  fail_unless_equals_int (g_list_length (buffers), 0);

  gst_check_drop_buffers ();

  gst_caps_replace (&event_caps, NULL);

  ASSERT_OBJECT_REFCOUNT (src, "src", 1);
  ASSERT_OBJECT_REFCOUNT (sink, "sink", 1);
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

/* Like test_sticky_caps_unlinked, but link before caps: */

GST_START_TEST (test_sticky_caps_flushing)
{
  GstCaps *caps;
  GstPadTemplate *src_template, *sink_template;
  GstPad *src, *sink;
  GstEvent *event;

  caps = gst_caps_from_string ("foo/bar, dummy=(int){1, 2}");
  src_template = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, caps);
  sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, caps);
  gst_caps_unref (caps);

  src = gst_pad_new_from_template (src_template, "src");
  fail_if (src == NULL);
  sink = gst_pad_new_from_template (sink_template, "sink");
  fail_if (sink == NULL);
  gst_pad_set_event_function (sink, sticky_event);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  gst_object_unref (src_template);
  gst_object_unref (sink_template);

  fail_unless (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (src, sink)));

  caps = gst_caps_from_string ("foo/bar, dummy=(int)1");
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  event = gst_event_new_caps (caps);

  gst_pad_set_active (src, TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  /* The caps event gets accepted by the source pad (and stored) */
  fail_unless (gst_pad_push_event (src, event) == TRUE);
  /* But wasn't forwarded since the sink pad is flushing (not activated) */
  fail_unless (event_caps == NULL);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* Activating will not forward the sticky event yet... */
  gst_pad_set_active (sink, TRUE);
  fail_unless (event_caps == NULL);

  /* ...but the first buffer will: */
  fail_unless (gst_pad_push (src, gst_buffer_new ()) == GST_FLOW_OK);
  fail_unless (event_caps == caps);
  fail_unless_equals_int (g_list_length (buffers), 1);

  gst_check_drop_buffers ();

  gst_caps_replace (&caps, NULL);
  gst_caps_replace (&event_caps, NULL);

  ASSERT_OBJECT_REFCOUNT (src, "src", 1);
  ASSERT_OBJECT_REFCOUNT (sink, "sink", 1);
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static gboolean
name_is_valid (const gchar * name, GstPadPresence presence)
{
  GstPadTemplate *new;
  GstCaps *any = gst_caps_new_any ();

  new = gst_pad_template_new (name, GST_PAD_SRC, presence, any);
  gst_caps_unref (any);
  if (new) {
    gst_object_unref (GST_OBJECT (new));
    return TRUE;
  }
  return FALSE;
}

GST_START_TEST (test_name_is_valid)
{
  gboolean result = FALSE;

  fail_unless (name_is_valid ("src", GST_PAD_ALWAYS));
  ASSERT_WARNING (name_is_valid ("src%", GST_PAD_ALWAYS));
  ASSERT_WARNING (result = name_is_valid ("src%d", GST_PAD_ALWAYS));
  fail_if (result);

  fail_unless (name_is_valid ("src", GST_PAD_REQUEST));
  ASSERT_WARNING (name_is_valid ("src%s%s", GST_PAD_REQUEST));
  ASSERT_WARNING (name_is_valid ("src%c", GST_PAD_REQUEST));
  ASSERT_WARNING (name_is_valid ("src%", GST_PAD_REQUEST));
  fail_unless (name_is_valid ("src%dsrc", GST_PAD_REQUEST));

  fail_unless (name_is_valid ("src", GST_PAD_SOMETIMES));
  fail_unless (name_is_valid ("src%c", GST_PAD_SOMETIMES));
}

GST_END_TEST;

static GstPadProbeReturn
_probe_handler (GstPad * pad, GstPadProbeInfo * info, gpointer userdata)
{
  GstPadProbeReturn ret = (GstPadProbeReturn) GPOINTER_TO_INT (userdata);

  /* If we are handling the data, we unref it */
  if (ret == GST_PAD_PROBE_HANDLED
      && !(GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_QUERY_BOTH)) {
    GST_DEBUG_OBJECT (pad, "Unreffing data");
    gst_mini_object_unref (info->data);
  }
  return ret;
}

static GstPadProbeReturn
_handled_probe_handler (GstPad * pad, GstPadProbeInfo * info, gpointer userdata)
{
  GstFlowReturn customflow = (GstFlowReturn) GPOINTER_TO_INT (userdata);

  /* We are handling the data, we unref it */
  if (!(GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_QUERY_BOTH))
    gst_mini_object_unref (info->data);
  GST_PAD_PROBE_INFO_FLOW_RETURN (info) = customflow;

  return GST_PAD_PROBE_HANDLED;
}

static GstPadProbeReturn
_cleaning_handled_probe_handler (GstPad * pad, GstPadProbeInfo * info,
    gpointer userdata)
{
  GstFlowReturn customflow = (GstFlowReturn) GPOINTER_TO_INT (userdata);

  /* We are handling the data, we unref it and we reset the data field */
  if (!(GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_QUERY_BOTH))
    gst_mini_object_unref (info->data);
  GST_PAD_PROBE_INFO_FLOW_RETURN (info) = customflow;
  GST_PAD_PROBE_INFO_DATA (info) = NULL;

  return GST_PAD_PROBE_HANDLED;
}



GST_START_TEST (test_events_query_unlinked)
{
  GstPad *src;
  GstCaps *caps;
  gulong id;
  GstQuery *query;

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);
  caps = gst_pad_get_allowed_caps (src);
  fail_unless (caps == NULL);

  caps = gst_caps_from_string ("foo/bar");

  gst_pad_set_active (src, TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  gst_pad_set_caps (src, caps);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  /* Doing a query on an unlinked pad will return FALSE */
  query = gst_query_new_duration (GST_FORMAT_TIME);
  fail_unless (gst_pad_peer_query (src, query) == FALSE);
  ASSERT_MINI_OBJECT_REFCOUNT (query, "query", 1);
  gst_query_unref (query);

  /* Add a probe that returns _DROP will make the event push return TRUE
   * even if not linked */
  GST_DEBUG ("event/query DROP");
  id = gst_pad_add_probe (src,
      GST_PAD_PROBE_TYPE_EVENT_BOTH | GST_PAD_PROBE_TYPE_QUERY_BOTH,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_DROP), NULL);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);
  /* Queries should still fail */
  query = gst_query_new_duration (GST_FORMAT_TIME);
  fail_unless (gst_pad_peer_query (src, query) == FALSE);
  ASSERT_MINI_OBJECT_REFCOUNT (query, "query", 1);
  gst_query_unref (query);
  gst_pad_remove_probe (src, id);

  /* Add a probe that returns _HANDLED will make the event push return TRUE
   * even if not linked */
  GST_DEBUG ("event/query HANDLED");
  id = gst_pad_add_probe (src,
      GST_PAD_PROBE_TYPE_EVENT_BOTH | GST_PAD_PROBE_TYPE_QUERY_BOTH,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_HANDLED), NULL);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* Queries will succeed */
  query = gst_query_new_duration (GST_FORMAT_TIME);
  fail_unless (gst_pad_peer_query (src, query) == TRUE);
  ASSERT_MINI_OBJECT_REFCOUNT (query, "query", 1);
  gst_query_unref (query);
  gst_pad_remove_probe (src, id);

  /* cleanup */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  ASSERT_OBJECT_REFCOUNT (src, "src", 1);

  gst_object_unref (src);

  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  gst_caps_unref (caps);
}

GST_END_TEST;

GST_START_TEST (test_push_unlinked)
{
  GstPad *src;
  GstCaps *caps;
  GstBuffer *buffer;
  gulong id;
  GstFlowReturn fl;

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);
  caps = gst_pad_get_allowed_caps (src);
  fail_unless (caps == NULL);

  caps = gst_caps_from_string ("foo/bar");

  /* pushing on an inactive pad will return wrong state */
  GST_DEBUG ("push buffer inactive");
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_FLUSHING);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);

  gst_pad_set_active (src, TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  GST_DEBUG ("push caps event inactive");
  gst_pad_set_caps (src, caps);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* pushing on an unlinked pad will drop the buffer */
  GST_DEBUG ("push buffer unlinked");
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_NOT_LINKED);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);

  /* adding a probe that returns _DROP will drop the buffer without trying
   * to chain */
  GST_DEBUG ("push buffer drop");
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_DROP), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);

  /* adding a probe that returns _HANDLED will drop the buffer without trying
   * to chain */
  GST_DEBUG ("push buffer handled");
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_HANDLED), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);

  /* adding a probe that returns _OK will still chain the buffer,
   * and hence drop because pad is unlinked */
  GST_DEBUG ("push buffer ok");
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_OK), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_NOT_LINKED);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);

  GST_DEBUG ("push buffer handled and custom return");
  for (fl = GST_FLOW_NOT_SUPPORTED; fl <= GST_FLOW_OK; fl += 1) {
    GST_DEBUG ("Testing with %s", gst_flow_get_name (fl));
    id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
        _handled_probe_handler, GINT_TO_POINTER (fl), NULL);
    buffer = gst_buffer_new ();
    gst_buffer_ref (buffer);
    fail_unless (gst_pad_push (src, buffer) == fl);
    ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
    gst_buffer_unref (buffer);
    gst_pad_remove_probe (src, id);

  }

  /* Same thing, except that this time we also set the info data field
   * to NULL in the probe. We can because we are returning _HANDLED */
  GST_DEBUG ("push buffer handled and custom return (with info data NULL'ed)");
  for (fl = GST_FLOW_NOT_SUPPORTED; fl <= GST_FLOW_OK; fl += 1) {
    GST_DEBUG ("Testing with %s", gst_flow_get_name (fl));
    id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
        _cleaning_handled_probe_handler, GINT_TO_POINTER (fl), NULL);
    buffer = gst_buffer_new ();
    gst_buffer_ref (buffer);
    fail_unless (gst_pad_push (src, buffer) == fl);
    ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
    gst_buffer_unref (buffer);
    gst_pad_remove_probe (src, id);

  }


  /* cleanup */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  ASSERT_OBJECT_REFCOUNT (src, "src", 1);

  gst_object_unref (src);

  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  gst_caps_unref (caps);
}

GST_END_TEST;

GST_START_TEST (test_push_linked)
{
  GstPad *src, *sink;
  GstPadLinkReturn plr;
  GstCaps *caps;
  GstBuffer *buffer;
  gulong id;

  /* setup */
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);

  caps = gst_caps_from_string ("foo/bar");
  /* one for me */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  gst_pad_set_active (src, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);

  gst_pad_set_caps (src, caps);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  gst_pad_set_active (sink, TRUE);
  /* one for me and one for each set_caps */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  buffer = gst_buffer_new ();

  /* test */
  /* pushing on a linked pad will drop the ref to the buffer */
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 2);
  gst_buffer_unref (buffer);
  fail_unless_equals_int (g_list_length (buffers), 1);
  buffer = GST_BUFFER (buffers->data);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  g_list_free (buffers);
  buffers = NULL;

  /* adding a probe that returns _DROP will drop the buffer without trying
   * to chain */
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_DROP), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);
  fail_unless_equals_int (g_list_length (buffers), 0);

  /* adding a probe that returns _OK will still chain the buffer */
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_OK), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  gst_pad_remove_probe (src, id);

  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 2);
  gst_buffer_unref (buffer);
  fail_unless_equals_int (g_list_length (buffers), 1);
  buffer = GST_BUFFER (buffers->data);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  g_list_free (buffers);
  buffers = NULL;

  /* adding a probe that returns _HANDLED will not chain the buffer */
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      _probe_handler, GINT_TO_POINTER (GST_PAD_PROBE_HANDLED), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_OK);
  gst_pad_remove_probe (src, id);

  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  gst_buffer_unref (buffer);
  fail_unless_equals_int (g_list_length (buffers), 0);
  g_list_free (buffers);
  buffers = NULL;

  /* teardown */
  gst_check_drop_buffers ();
  gst_pad_unlink (src, sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  gst_object_unref (src);
  gst_object_unref (sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  gst_caps_unref (caps);
}

GST_END_TEST;

GST_START_TEST (test_push_linked_flushing)
{
  GstPad *src, *sink;
  GstCaps *caps;
  GstPadLinkReturn plr;
  GstBuffer *buffer;
  gulong id;

  /* setup */
  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  caps = gst_pad_get_allowed_caps (src);
  fail_unless (caps == NULL);
  caps = gst_pad_get_allowed_caps (sink);
  fail_unless (caps == NULL);

  caps = gst_caps_from_string ("foo/bar");
  /* one for me */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);

  gst_pad_set_active (src, TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  gst_pad_set_caps (src, caps);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);
  /* need to activate to make it accept the caps */
  gst_pad_set_active (sink, TRUE);
  /* one for me and one for each set_caps */
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);

  /* not activating the pads here, which keeps them flushing */
  gst_pad_set_active (src, FALSE);
  gst_pad_set_active (sink, FALSE);

  /* pushing on a flushing pad will drop the buffer */
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_FLUSHING);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  fail_unless_equals_int (g_list_length (buffers), 0);
  gst_buffer_unref (buffer);

  gst_pad_set_active (src, TRUE);
  gst_pad_set_active (sink, FALSE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  gst_pad_set_caps (src, caps);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);
  /* adding a probe that returns _DROP will drop the buffer without trying
   * to chain */
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER, _probe_handler,
      GINT_TO_POINTER (GST_PAD_PROBE_DROP), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_FLUSHING);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  fail_unless_equals_int (g_list_length (buffers), 0);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);

  /* adding a probe that returns _OK will still chain the buffer,
   * and hence drop because pad is flushing */
  id = gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER, _probe_handler,
      GINT_TO_POINTER (GST_PAD_PROBE_OK), NULL);
  buffer = gst_buffer_new ();
  gst_buffer_ref (buffer);
  fail_unless (gst_pad_push (src, buffer) == GST_FLOW_FLUSHING);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  fail_unless_equals_int (g_list_length (buffers), 0);
  gst_buffer_unref (buffer);
  gst_pad_remove_probe (src, id);

  /* cleanup */
  gst_check_drop_buffers ();
  ASSERT_CAPS_REFCOUNT (caps, "caps", 2);
  ASSERT_OBJECT_REFCOUNT (src, "src", 1);
  gst_pad_link (src, sink);
  gst_object_unref (src);
  gst_object_unref (sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  gst_caps_unref (caps);
}

GST_END_TEST;

static GstBuffer *
buffer_from_string (const gchar * str)
{
  guint size;
  GstBuffer *buf;

  size = strlen (str);
  buf = gst_buffer_new_and_alloc (size);

  gst_buffer_fill (buf, 0, str, size);

  return buf;
}

static gboolean
buffer_compare (GstBuffer * buf, const gchar * str, gsize size)
{
  gboolean res;
  GstMapInfo info;

  fail_unless (gst_buffer_map (buf, &info, GST_MAP_READ));
  res = memcmp (info.data, str, size) == 0;
  GST_MEMDUMP ("buffer  data", info.data, size);
  GST_MEMDUMP ("compare data", (guint8 *) str, size);
  GST_DEBUG ("buffers match: %s", res ? "yes" : "no");
  gst_buffer_unmap (buf, &info);

  return res;
}

GST_START_TEST (test_push_buffer_list_compat)
{
  GstPad *src, *sink;
  GstPadLinkReturn plr;
  GstCaps *caps;
  GstBufferList *list;
  GstBuffer *buffer;

  /* setup */
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  /* leave chainlistfunc unset */

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);

  caps = gst_caps_from_string ("foo/bar");

  gst_pad_set_active (src, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);

  gst_pad_set_caps (src, caps);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  gst_pad_set_active (sink, TRUE);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));

  list = gst_buffer_list_new ();

  /* test */
  /* adding to a buffer list will drop the ref to the buffer */
  gst_buffer_list_add (list, buffer_from_string ("ListGroup"));
  gst_buffer_list_add (list, buffer_from_string ("AnotherListGroup"));

  fail_unless (gst_pad_push_list (src, list) == GST_FLOW_OK);
  fail_unless_equals_int (g_list_length (buffers), 2);
  buffer = GST_BUFFER (buffers->data);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  fail_unless (buffer_compare (buffer, "ListGroup", 9));
  gst_buffer_unref (buffer);
  buffers = g_list_delete_link (buffers, buffers);
  buffer = GST_BUFFER (buffers->data);
  ASSERT_MINI_OBJECT_REFCOUNT (buffer, "buffer", 1);
  fail_unless (buffer_compare (buffer, "AnotherListGroup", 16));
  gst_buffer_unref (buffer);
  buffers = g_list_delete_link (buffers, buffers);
  fail_unless (buffers == NULL);

  /* teardown */
  gst_check_drop_buffers ();
  gst_pad_unlink (src, sink);
  gst_object_unref (src);
  gst_object_unref (sink);
  ASSERT_CAPS_REFCOUNT (caps, "caps", 1);
  gst_caps_unref (caps);
}

GST_END_TEST;

GST_START_TEST (test_flowreturn)
{
  GstFlowReturn ret;
  GQuark quark;

  /* test some of the macros */
  ret = GST_FLOW_EOS;
  fail_if (strcmp (gst_flow_get_name (ret), "eos"));
  quark = gst_flow_to_quark (ret);
  fail_if (strcmp (g_quark_to_string (quark), "eos"));

  /* custom returns */
  ret = GST_FLOW_CUSTOM_SUCCESS;
  fail_if (strcmp (gst_flow_get_name (ret), "custom-success"));
  quark = gst_flow_to_quark (ret);
  fail_if (strcmp (g_quark_to_string (quark), "custom-success"));

  ret = GST_FLOW_CUSTOM_ERROR;
  fail_if (strcmp (gst_flow_get_name (ret), "custom-error"));
  quark = gst_flow_to_quark (ret);
  fail_if (strcmp (g_quark_to_string (quark), "custom-error"));

  /* custom returns clamping */
  ret = GST_FLOW_CUSTOM_SUCCESS + 2;
  fail_if (strcmp (gst_flow_get_name (ret), "custom-success"));
  quark = gst_flow_to_quark (ret);
  fail_if (strcmp (g_quark_to_string (quark), "custom-success"));

  ret = GST_FLOW_CUSTOM_ERROR - 2;
  fail_if (strcmp (gst_flow_get_name (ret), "custom-error"));
  quark = gst_flow_to_quark (ret);
  fail_if (strcmp (g_quark_to_string (quark), "custom-error"));

  /* unknown values */
  ret = GST_FLOW_CUSTOM_ERROR + 2;
  fail_if (strcmp (gst_flow_get_name (ret), "unknown"));
  quark = gst_flow_to_quark (ret);
  fail_unless (quark == 0);
}

GST_END_TEST;

GST_START_TEST (test_push_negotiation)
{
  GstPad *src, *sink;
  GstPadLinkReturn plr;
  GstCaps *srccaps =
      gst_caps_from_string ("audio/x-raw,width={16,32},depth={16,32}");
  GstCaps *sinkcaps =
      gst_caps_from_string ("audio/x-raw,width=32,depth={16,32}");
  GstPadTemplate *src_template;
  GstPadTemplate *sink_template;
  GstCaps *caps;

  /* setup */
  src_template = gst_pad_template_new ("src", GST_PAD_SRC,
      GST_PAD_ALWAYS, srccaps);
  sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
      GST_PAD_ALWAYS, sinkcaps);
  gst_caps_unref (srccaps);
  gst_caps_unref (sinkcaps);

  sink = gst_pad_new_from_template (sink_template, "sink");
  fail_if (sink == NULL);
  gst_pad_set_chain_function (sink, gst_check_chain_func);

  src = gst_pad_new_from_template (src_template, "src");
  fail_if (src == NULL);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));

  /* activate pads */
  gst_pad_set_active (src, TRUE);
  gst_pad_set_active (sink, TRUE);

  caps = gst_caps_from_string ("audio/x-raw,width=16,depth=16");

  /* Should fail if src pad caps are incompatible with sink pad caps */
  gst_pad_set_caps (src, caps);
  fail_unless (gst_pad_set_caps (sink, caps) == FALSE);

  /* teardown */
  gst_check_drop_buffers ();
  gst_pad_unlink (src, sink);
  gst_object_unref (src);
  gst_object_unref (sink);
  gst_caps_unref (caps);
  gst_object_unref (sink_template);
  gst_object_unref (src_template);
}

GST_END_TEST;

/* see that an unref also unlinks the pads */
GST_START_TEST (test_src_unref_unlink)
{
  GstPad *src, *sink;
  GstCaps *caps;
  GstPadLinkReturn plr;

  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);

  caps = gst_caps_from_string ("foo/bar");

  gst_pad_set_active (src, TRUE);
  gst_pad_set_caps (src, caps);
  gst_pad_set_active (sink, TRUE);
  gst_pad_set_caps (sink, caps);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));

  /* unref the srcpad */
  gst_object_unref (src);

  /* sink should be unlinked now */
  fail_if (gst_pad_is_linked (sink));

  /* cleanup */
  gst_object_unref (sink);
  gst_caps_unref (caps);
}

GST_END_TEST;

/* see that an unref also unlinks the pads */
GST_START_TEST (test_sink_unref_unlink)
{
  GstPad *src, *sink;
  GstCaps *caps;
  GstPadLinkReturn plr;

  sink = gst_pad_new ("sink", GST_PAD_SINK);
  fail_if (sink == NULL);

  src = gst_pad_new ("src", GST_PAD_SRC);
  fail_if (src == NULL);

  caps = gst_caps_from_string ("foo/bar");

  gst_pad_set_active (src, TRUE);
  gst_pad_set_caps (src, caps);
  gst_pad_set_active (sink, TRUE);
  gst_pad_set_caps (sink, caps);

  plr = gst_pad_link (src, sink);
  fail_unless (GST_PAD_LINK_SUCCESSFUL (plr));

  /* unref the sinkpad */
  gst_object_unref (sink);

  /* src should be unlinked now */
  fail_if (gst_pad_is_linked (src));

  /* cleanup */
  gst_object_unref (src);
  gst_caps_unref (caps);
}

GST_END_TEST;

static gulong id;

static GstPadProbeReturn
block_async_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  gboolean *bool_user_data = (gboolean *) user_data;

  fail_unless ((info->type & GST_PAD_PROBE_TYPE_BLOCK) != 0);

  /* here we should have blocked == 0 unblocked == 0 */
  fail_unless (bool_user_data[0] == FALSE);
  fail_unless (bool_user_data[1] == FALSE);

  bool_user_data[0] = TRUE;

  gst_pad_remove_probe (pad, id);
  bool_user_data[1] = TRUE;

  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_block_async)
{
  GstPad *pad;
  /* we set data[0] = TRUE when the pad is blocked, data[1] = TRUE when it's
   * unblocked */
  gboolean data[2] = { FALSE, FALSE };

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);

  gst_pad_set_active (pad, TRUE);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK, block_async_cb, &data,
      NULL);

  fail_unless (data[0] == FALSE);
  fail_unless (data[1] == FALSE);
  gst_pad_push (pad, gst_buffer_new ());

  gst_object_unref (pad);
}

GST_END_TEST;

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

static gpointer
push_buffer_async (GstPad * pad)
{
  gpointer ret = GINT_TO_POINTER (gst_pad_push (pad, gst_buffer_new ()));
  gst_object_unref (pad);
  return ret;
}

static void
test_pad_blocking_with_type (GstPadProbeType type)
{
  GstPad *pad;
  GThread *thread;
  GstFlowReturn ret;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);

  gst_pad_set_active (pad, TRUE);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  id = gst_pad_add_probe (pad, type, block_async_cb_return_ok, NULL, NULL);

  thread = g_thread_try_new ("gst-check", (GThreadFunc) push_buffer_async,
      gst_object_ref (pad), NULL);

  /* wait for the block */
  while (!gst_pad_is_blocking (pad)) {
    g_usleep (10000);
  }

  /* stop with flushing */
  gst_pad_push_event (pad, gst_event_new_flush_start ());

  /* get return value from push */
  ret = GPOINTER_TO_INT (g_thread_join (thread));
  /* unflush now */
  gst_pad_push_event (pad, gst_event_new_flush_stop (FALSE));
  /* must be wrong state */
  fail_unless (ret == GST_FLOW_FLUSHING);

  gst_object_unref (pad);
}

GST_START_TEST (test_pad_blocking_with_probe_type_block)
{
  test_pad_blocking_with_type (GST_PAD_PROBE_TYPE_BLOCK);
}

GST_END_TEST;

GST_START_TEST (test_pad_blocking_with_probe_type_blocking)
{
  test_pad_blocking_with_type (GST_PAD_PROBE_TYPE_BLOCKING);
}

GST_END_TEST;

static gboolean idle_probe_running;

static GstFlowReturn
idletest_sink_pad_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
  if (idle_probe_running)
    fail ("Should not be reached");
  gst_buffer_unref (buf);
  return GST_FLOW_OK;
}

static GstPadProbeReturn
idle_probe_wait (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  /* it is ok to have a probe called multiple times but it is not
   * acceptable in our scenario */
  fail_if (idle_probe_running);

  idle_probe_running = TRUE;
  while (idle_probe_running) {
    g_usleep (10000);
  }

  return GST_PAD_PROBE_REMOVE;
}

static gpointer
add_idle_probe_async (GstPad * pad)
{
  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_IDLE, idle_probe_wait, NULL, NULL);
  gst_object_unref (pad);

  return NULL;
}

GST_START_TEST (test_pad_blocking_with_probe_type_idle)
{
  GstPad *srcpad, *sinkpad;
  GThread *idle_thread, *thread;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);

  gst_pad_set_chain_function (sinkpad, idletest_sink_pad_chain);

  fail_unless (gst_pad_link (srcpad, sinkpad) == GST_PAD_LINK_OK);

  gst_pad_set_active (sinkpad, TRUE);
  gst_pad_set_active (srcpad, TRUE);

  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  idle_probe_running = FALSE;
  idle_thread =
      g_thread_try_new ("gst-check", (GThreadFunc) add_idle_probe_async,
      gst_object_ref (srcpad), NULL);

  /* wait for the idle function to signal it is being called */
  while (!idle_probe_running) {
    g_usleep (10000);
  }

  thread = g_thread_try_new ("gst-check", (GThreadFunc) push_buffer_async,
      gst_object_ref (srcpad), NULL);

  while (!gst_pad_is_blocking (srcpad)) {
    g_usleep (10000);
  }

  idle_probe_running = FALSE;

  g_thread_join (idle_thread);
  g_thread_join (thread);
  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

static gboolean pull_probe_called;
static gboolean pull_probe_called_with_bad_type;
static gboolean pull_probe_called_with_bad_data;

static GstPadProbeReturn
probe_pull_buffer_cb_check_buffer_return_ok (GstPad * pad,
    GstPadProbeInfo * info, gpointer user_data)
{
  if (info->type & GST_PAD_PROBE_TYPE_BUFFER) {
    if (GST_IS_BUFFER (info->data))
      pull_probe_called = TRUE;
    else
      pull_probe_called_with_bad_data = TRUE;
  } else {
    /* shouldn't be called */
    pull_probe_called_with_bad_type = TRUE;
  }
  return GST_PAD_PROBE_OK;
}

static GstFlowReturn
test_probe_pull_getrange (GstPad * pad, GstObject * parent, guint64 offset,
    guint length, GstBuffer ** buf)
{
  *buf = gst_buffer_new ();
  return GST_FLOW_OK;
}

static gboolean
test_probe_pull_activate_pull (GstPad * pad, GstObject * object)
{
  return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE);
}

static gpointer
pull_range_async (GstPad * pad)
{
  GstBuffer *buf = NULL;
  GstFlowReturn res = gst_pad_pull_range (pad, 0, 100, &buf);
  if (buf)
    gst_buffer_unref (buf);
  gst_object_unref (pad);

  return GINT_TO_POINTER (res);
}

GST_START_TEST (test_pad_probe_pull)
{
  GstPad *srcpad, *sinkpad;
  GThread *thread;
  GstFlowReturn ret;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);

  gst_pad_set_getrange_function (srcpad, test_probe_pull_getrange);
  gst_pad_set_activate_function (sinkpad, test_probe_pull_activate_pull);
  gst_pad_link (srcpad, sinkpad);

  gst_pad_set_active (sinkpad, TRUE);
  gst_pad_set_active (srcpad, TRUE);

  id = gst_pad_add_probe (sinkpad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_PULL,
      block_async_cb_return_ok, NULL, NULL);

  thread = g_thread_try_new ("gst-check", (GThreadFunc) pull_range_async,
      gst_object_ref (sinkpad), NULL);

  /* wait for the block */
  while (!gst_pad_is_blocking (sinkpad)) {
    g_usleep (10000);
  }

  /* stop with flushing */
  gst_pad_push_event (srcpad, gst_event_new_flush_start ());

  /* get return value from push */
  ret = GPOINTER_TO_INT (g_thread_join (thread));
  /* unflush now */
  gst_pad_push_event (srcpad, gst_event_new_flush_stop (FALSE));
  /* must be wrong state */
  fail_unless (ret == GST_FLOW_FLUSHING);

  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

static gboolean idle_probe_called;
static gboolean get_range_wait;
static gboolean getrange_waiting;

static GstPadProbeReturn
idle_cb_return_ok (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  idle_probe_called = TRUE;
  return GST_PAD_PROBE_OK;
}

static GstFlowReturn
test_probe_pull_getrange_wait (GstPad * pad, GstObject * parent, guint64 offset,
    guint length, GstBuffer ** buf)
{
  getrange_waiting = TRUE;

  *buf = gst_buffer_new ();
  while (get_range_wait) {
    g_usleep (10000);
  }

  getrange_waiting = FALSE;
  return GST_FLOW_OK;
}

GST_START_TEST (test_pad_probe_pull_idle)
{
  GstPad *srcpad, *sinkpad;
  GThread *thread;
  GstFlowReturn ret;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);

  gst_pad_set_getrange_function (srcpad, test_probe_pull_getrange_wait);
  gst_pad_set_activate_function (sinkpad, test_probe_pull_activate_pull);
  gst_pad_link (srcpad, sinkpad);

  gst_pad_set_active (sinkpad, TRUE);
  gst_pad_set_active (srcpad, TRUE);

  idle_probe_called = FALSE;
  get_range_wait = TRUE;
  thread = g_thread_try_new ("gst-check", (GThreadFunc) pull_range_async,
      gst_object_ref (sinkpad), NULL);

  /* wait for the block */
  while (!getrange_waiting) {
    g_usleep (10000);
  }

  id = gst_pad_add_probe (sinkpad,
      GST_PAD_PROBE_TYPE_IDLE | GST_PAD_PROBE_TYPE_PULL,
      idle_cb_return_ok, NULL, NULL);

  fail_if (idle_probe_called);

  get_range_wait = FALSE;
  while (getrange_waiting) {
    g_usleep (10000);
  }
  while (!idle_probe_called) {
    g_usleep (10000);
  }

  ret = GPOINTER_TO_INT (g_thread_join (thread));
  fail_unless (ret == GST_FLOW_OK);
  gst_pad_set_active (srcpad, FALSE);
  gst_pad_set_active (sinkpad, FALSE);
  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;


GST_START_TEST (test_pad_probe_pull_buffer)
{
  GstPad *srcpad, *sinkpad;
  GThread *thread;
  GstFlowReturn ret;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);

  gst_pad_set_getrange_function (srcpad, test_probe_pull_getrange);
  gst_pad_set_activate_function (sinkpad, test_probe_pull_activate_pull);
  gst_pad_link (srcpad, sinkpad);

  gst_pad_set_active (sinkpad, TRUE);
  gst_pad_set_active (srcpad, TRUE);

  id = gst_pad_add_probe (sinkpad,
      GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_PULL,
      probe_pull_buffer_cb_check_buffer_return_ok, NULL, NULL);

  pull_probe_called = FALSE;
  pull_probe_called_with_bad_type = FALSE;
  pull_probe_called_with_bad_data = FALSE;

  thread = g_thread_try_new ("gst-check", (GThreadFunc) pull_range_async,
      gst_object_ref (sinkpad), NULL);

  /* wait for the block */
  while (!pull_probe_called && !pull_probe_called_with_bad_data
      && !pull_probe_called_with_bad_type) {
    g_usleep (10000);
  }

  fail_unless (pull_probe_called);
  fail_if (pull_probe_called_with_bad_data);
  fail_if (pull_probe_called_with_bad_type);

  /* get return value from push */
  ret = GPOINTER_TO_INT (g_thread_join (thread));
  fail_unless (ret == GST_FLOW_OK);

  gst_pad_set_active (sinkpad, FALSE);
  gst_pad_set_active (srcpad, FALSE);
  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

static gboolean pad_probe_remove_notifiy_called = FALSE;

static GstPadProbeReturn
probe_remove_self_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  gst_pad_remove_probe (pad, info->id);

  return GST_PAD_PROBE_REMOVE;
}

static void
probe_remove_notify_cb (gpointer data)
{
  fail_unless (pad_probe_remove_notifiy_called == FALSE);
  pad_probe_remove_notifiy_called = TRUE;
}

GST_START_TEST (test_pad_probe_remove)
{
  GstPad *pad;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);

  gst_pad_set_active (pad, TRUE);
  fail_unless (pad->num_probes == 0);
  fail_unless (pad->num_blocked == 0);
  gst_pad_add_probe (pad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
      probe_remove_self_cb, NULL, probe_remove_notify_cb);
  fail_unless (pad->num_probes == 1);
  fail_unless (pad->num_blocked == 1);

  pad_probe_remove_notifiy_called = FALSE;
  gst_pad_push_event (pad, gst_event_new_stream_start ("asda"));

  fail_unless (pad->num_probes == 0);
  fail_unless (pad->num_blocked == 0);

  gst_object_unref (pad);
}

GST_END_TEST;

GST_START_TEST (test_pad_disjoint_blocks_probe_remove)
{
  GstPad *pad;

  /* Test that installing 2 separate blocking probes - one on events
   * and one on buffers, and then removing the blocking event probe
   * releases the dataflow until a buffer is caught
   *
   * https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/658
   */
  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);

  gst_pad_set_active (pad, TRUE);
  fail_unless (pad->num_probes == 0);
  fail_unless (pad->num_blocked == 0);
  gst_pad_add_probe (pad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
      probe_remove_self_cb, NULL, probe_remove_notify_cb);
  fail_unless (pad->num_probes == 1);
  fail_unless (pad->num_blocked == 1);

  gst_pad_add_probe (pad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
      probe_remove_self_cb, NULL, probe_remove_notify_cb);
  fail_unless (pad->num_probes == 2);
  fail_unless (pad->num_blocked == 2);

  pad_probe_remove_notifiy_called = FALSE;
  gst_pad_push_event (pad, gst_event_new_stream_start ("asda"));

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  pad_probe_remove_notifiy_called = FALSE;
  gst_pad_push (pad, gst_buffer_new ());

  fail_unless (pad->num_probes == 0);
  fail_unless (pad->num_blocked == 0);

  gst_object_unref (pad);
}

GST_END_TEST;


typedef struct
{
  gulong probe_id;
  GstPad *probe_pad;
  GThread *thread;
} BlockReplaceProbeHelper;

static gpointer
unblock_probe_thread (gpointer user_data)
{
  BlockReplaceProbeHelper *helper = user_data;

  GST_INFO_OBJECT (helper->probe_pad, "removing probe to unblock pad");
  gst_pad_remove_probe (helper->probe_pad, helper->probe_id);
  return NULL;
}

static GstPadProbeReturn
block_and_replace_buffer_probe_cb (GstPad * pad, GstPadProbeInfo * info,
    gpointer user_data)
{
  BlockReplaceProbeHelper *helper = user_data;

  GST_INFO_OBJECT (pad, "about to block pad, replacing buffer");

  /* we want to block, but also drop this buffer */
  gst_buffer_unref (GST_BUFFER (info->data));
  info->data = NULL;

  helper->thread =
      g_thread_new ("gst-pad-test-thread", unblock_probe_thread, helper);

  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_pad_probe_block_and_drop_buffer)
{
  BlockReplaceProbeHelper helper;
  GstFlowReturn flow;
  GstPad *src, *sink;

  src = gst_pad_new ("src", GST_PAD_SRC);
  gst_pad_set_active (src, TRUE);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  gst_pad_set_active (sink, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  fail_unless_equals_int (gst_pad_link (src, sink), GST_PAD_LINK_OK);

  helper.probe_id = gst_pad_add_probe (src,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
      block_and_replace_buffer_probe_cb, &helper, NULL);
  helper.probe_pad = src;

  /* push a buffer so the events are propagated downstream */
  flow = gst_pad_push (src, gst_buffer_new ());

  g_thread_join (helper.thread);

  fail_unless_equals_int (flow, GST_FLOW_OK);

  /* no buffer should have made it through to the sink pad, and especially
   * not a NULL pointer buffer */
  fail_if (buffers && buffers->data == NULL);
  fail_unless (buffers == NULL);

  gst_check_drop_buffers ();
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static GstPadProbeReturn
probe_block_ok (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  gboolean *called = user_data;
  if (called)
    *called = TRUE;
  return GST_PAD_PROBE_OK;
}

static GstPadProbeReturn
probe_block_remove (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  gboolean *called = user_data;
  *called = TRUE;
  return GST_PAD_PROBE_REMOVE;
}

GST_START_TEST (test_pad_probe_block_add_remove)
{
  GstPad *pad;
  GThread *thread;
  gulong probe_a, probe_b;
  gboolean called;
  guint r;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);

  gst_pad_set_active (pad, TRUE);
  fail_unless (pad->num_probes == 0);
  fail_unless (pad->num_blocked == 0);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  probe_a = gst_pad_add_probe (pad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
      probe_block_ok, NULL, NULL);

  fail_unless (pad->num_probes == 1);
  fail_unless (pad->num_blocked == 1);

  thread = g_thread_try_new ("gst-check", (GThreadFunc) push_buffer_async,
      gst_object_ref (pad), NULL);

  /* wait for the block */
  while (!gst_pad_is_blocking (pad))
    g_thread_yield ();

  /* alternate 2 probes 100 times */
  for (r = 0; r < 100; r++) {
    called = FALSE;
    probe_b = gst_pad_add_probe (pad,
        GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
        probe_block_ok, &called, NULL);

    gst_pad_remove_probe (pad, probe_a);

    /* wait for the callback */
    while (!called)
      g_thread_yield ();

    called = FALSE;
    probe_a = gst_pad_add_probe (pad,
        GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
        probe_block_ok, &called, NULL);

    gst_pad_remove_probe (pad, probe_b);

    /* wait for the callback */
    while (!called)
      g_thread_yield ();
  }

  called = FALSE;
  gst_pad_add_probe (pad,
      GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
      probe_block_remove, &called, NULL);

  gst_pad_remove_probe (pad, probe_a);

  /* wait for the callback */
  while (!called)
    g_thread_yield ();

  /* wait for the unblock */
  while (gst_pad_is_blocking (pad))
    g_thread_yield ();

  gst_object_unref (pad);
  g_thread_join (thread);
}

GST_END_TEST;

static gboolean src_flush_start_probe_called = FALSE;
static gboolean src_flush_stop_probe_called = FALSE;
static gboolean sink_flush_start_probe_called = FALSE;
static gboolean sink_flush_stop_probe_called = FALSE;

static GstPadProbeReturn
flush_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstEvent *event;

  if (!(GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_EVENT_FLUSH))
    goto out;

  event = gst_pad_probe_info_get_event (info);
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_FLUSH_START:
      if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
        src_flush_start_probe_called = TRUE;
      else
        sink_flush_start_probe_called = TRUE;
      break;
    case GST_EVENT_FLUSH_STOP:
      if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC)
        src_flush_stop_probe_called = TRUE;
      else
        sink_flush_stop_probe_called = TRUE;
      break;
    default:
      break;
  }

out:
  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_pad_probe_flush_events)
{
  GstPad *src, *sink;

  src = gst_pad_new ("src", GST_PAD_SRC);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  gst_pad_set_active (src, TRUE);
  gst_pad_set_active (sink, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  fail_unless (gst_pad_link (src, sink) == GST_PAD_LINK_OK);

  gst_pad_add_probe (src,
      GST_PAD_PROBE_TYPE_PUSH | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM |
      GST_PAD_PROBE_TYPE_EVENT_FLUSH, flush_probe_cb, NULL, NULL);
  gst_pad_add_probe (sink,
      GST_PAD_PROBE_TYPE_PUSH | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM |
      GST_PAD_PROBE_TYPE_EVENT_FLUSH, flush_probe_cb, NULL, NULL);

  gst_pad_push_event (src, gst_event_new_flush_start ());
  gst_pad_push_event (src, gst_event_new_flush_stop (TRUE));

  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  /* push a buffer so the events are propagated downstream */
  gst_pad_push (src, gst_buffer_new ());

  fail_unless (src_flush_start_probe_called);
  fail_unless (src_flush_stop_probe_called);
  fail_unless (sink_flush_start_probe_called);
  fail_unless (sink_flush_stop_probe_called);

  gst_check_drop_buffers ();
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static gboolean probe_was_called;

static GstPadProbeReturn
flush_events_only_probe (GstPad * pad, GstPadProbeInfo * info, gpointer data)
{
  GST_LOG_OBJECT (pad, "%" GST_PTR_FORMAT, GST_PAD_PROBE_INFO_DATA (info));

  probe_was_called = TRUE;

  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_pad_probe_flush_events_only)
{
  GstPad *src, *sink;

  src = gst_pad_new ("src", GST_PAD_SRC);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  gst_pad_set_active (src, TRUE);
  gst_pad_set_active (sink, TRUE);

  fail_unless (gst_pad_link (src, sink) == GST_PAD_LINK_OK);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);

  gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_EVENT_FLUSH,
      flush_events_only_probe, NULL, NULL);

  probe_was_called = FALSE;
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);
  fail_if (probe_was_called);

  fail_unless_equals_int (gst_pad_push (src, gst_buffer_new ()), GST_FLOW_OK);
  fail_if (probe_was_called);

  gst_pad_push_event (src, gst_event_new_flush_start ());
  fail_unless (probe_was_called);

  probe_was_called = FALSE;
  gst_pad_push_event (src, gst_event_new_flush_stop (TRUE));
  fail_unless (probe_was_called);

  gst_check_drop_buffers ();
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

#define NUM_PROBES 4
static guint count;

static GstPadProbeReturn
order_others_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  *(guint *) (user_data) = ++count;

  return GST_PAD_PROBE_REMOVE;
}

GST_START_TEST (test_pad_probe_call_order)
{
  GstFlowReturn flow;
  GstPad *src, *sink;
  guint counters[NUM_PROBES];
  guint i;

  src = gst_pad_new ("src", GST_PAD_SRC);
  gst_pad_set_active (src, TRUE);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  gst_pad_set_active (sink, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  fail_unless_equals_int (gst_pad_link (src, sink), GST_PAD_LINK_OK);

  for (i = 0; i < NUM_PROBES; i++) {
    gst_pad_add_probe (src,
        GST_PAD_PROBE_TYPE_BUFFER, order_others_probe_cb, &(counters[i]), NULL);
  }

  /* push a buffer so the events are propagated downstream */
  flow = gst_pad_push (src, gst_buffer_new ());
  fail_unless_equals_int (flow, GST_FLOW_OK);

  for (i = 0; i < NUM_PROBES; i++) {
    fail_unless (counters[i] == i + 1);
  }

  gst_check_drop_buffers ();
  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static GstPadProbeReturn
buffers_probe_handled (GstPad * pad, GstPadProbeInfo * info, gpointer gp)
{
  if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
    GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);

    GST_DEBUG_OBJECT (pad, "buffer: %" GST_PTR_FORMAT ", refcount: %d",
        buffer, (GST_MINI_OBJECT (buffer))->refcount);
    gst_buffer_unref (buffer);
  }

  return GST_PAD_PROBE_HANDLED;
}

static GstPadProbeReturn
buffers_probe_drop (GstPad * pad, GstPadProbeInfo * info, gboolean * called)
{
  if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_BUFFER) {
    GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);

    GST_DEBUG_OBJECT (pad, "buffer: %" GST_PTR_FORMAT ", refcount: %d",
        buffer, (GST_MINI_OBJECT (buffer))->refcount);
    *called = TRUE;
  }

  return GST_PAD_PROBE_DROP;
}

GST_START_TEST (test_pad_probe_handled_and_drop)
{
  GstFlowReturn flow;
  GstPad *src, *sink;
  gboolean called;

  src = gst_pad_new ("src", GST_PAD_SRC);
  gst_pad_set_active (src, TRUE);
  sink = gst_pad_new ("sink", GST_PAD_SINK);
  gst_pad_set_chain_function (sink, gst_check_chain_func);
  gst_pad_set_active (sink, TRUE);

  fail_unless (gst_pad_push_event (src,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (src,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  fail_unless_equals_int (gst_pad_link (src, sink), GST_PAD_LINK_OK);

  gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      (GstPadProbeCallback) buffers_probe_handled, NULL, NULL);
  gst_pad_add_probe (src, GST_PAD_PROBE_TYPE_BUFFER,
      (GstPadProbeCallback) buffers_probe_drop, &called, NULL);

  called = FALSE;
  flow = gst_pad_push (src, gst_buffer_new ());
  fail_unless_equals_int (flow, GST_FLOW_OK);
  fail_if (called);

  /* no buffer should have made it through to the sink pad, and especially
   * not a NULL pointer buffer */
  fail_if (buffers && buffers->data == NULL);
  fail_unless (buffers == NULL);

  gst_object_unref (src);
  gst_object_unref (sink);
}

GST_END_TEST;

static gboolean got_notify;

static void
caps_notify (GstPad * pad, GParamSpec * spec, gpointer data)
{
  got_notify = TRUE;
}

static void
test_queue_src_caps_notify (gboolean link_queue)
{
  GstElement *queue;
  GstPad *src, *sink, *another_pad;
  GstCaps *caps;

  queue = gst_element_factory_make ("queue", NULL);
  fail_unless (queue != NULL);

  src = gst_element_get_static_pad (queue, "src");
  fail_unless (src != NULL);

  sink = gst_element_get_static_pad (queue, "sink");
  fail_unless (sink != NULL);

  if (link_queue) {
    another_pad = gst_pad_new ("sink", GST_PAD_SINK);
    fail_unless (another_pad != NULL);
    gst_pad_set_active (another_pad, TRUE);

    gst_pad_link_full (src, another_pad, GST_PAD_LINK_CHECK_NOTHING);
  } else {
    another_pad = NULL;
  }

  gst_element_set_state (queue, GST_STATE_PLAYING);

  got_notify = FALSE;

  g_signal_connect (src, "notify::caps", G_CALLBACK (caps_notify), NULL);

  caps = gst_caps_from_string ("caps");
  gst_pad_send_event (sink, gst_event_new_caps (caps));
  gst_caps_unref (caps);

  while (got_notify == FALSE)
    g_usleep (10000);

  gst_element_set_state (queue, GST_STATE_NULL);

  gst_object_unref (src);
  gst_object_unref (sink);
  gst_object_unref (queue);
  if (another_pad) {
    gst_object_unref (another_pad);
  }
}

GST_START_TEST (test_queue_src_caps_notify_linked)
{
  test_queue_src_caps_notify (TRUE);
}

GST_END_TEST
GST_START_TEST (test_queue_src_caps_notify_not_linked)
{
  /* This test will fail because queue doesn't set the caps
     on src pad unless it is linked */
  test_queue_src_caps_notify (FALSE);
}

GST_END_TEST;

#if 0
static void
block_async_second (GstPad * pad, gboolean blocked, gpointer user_data)
{
  gst_pad_set_blocked (pad, FALSE, unblock_async_cb, NULL, NULL);
}

static void
block_async_first (GstPad * pad, gboolean blocked, gpointer user_data)
{
  static int n_calls = 0;
  gboolean *bool_user_data = (gboolean *) user_data;

  if (++n_calls > 1)
    /* we expect this callback to be called only once */
    g_warn_if_reached ();

  *bool_user_data = blocked;

  /* replace block_async_first with block_async_second so next time the pad is
   * blocked the latter should be called */
  gst_pad_set_blocked (pad, TRUE, block_async_second, NULL, NULL);

  /* unblock temporarily, in the next push block_async_second should be called
   */
  gst_pad_push_event (pad, gst_event_new_flush_start ());
}

GST_START_TEST (test_block_async_replace_callback)
{
  GstPad *pad;
  gboolean blocked;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);
  gst_pad_set_active (pad, TRUE);

  gst_pad_set_blocked (pad, TRUE, block_async_first, &blocked, NULL);
  blocked = FALSE;

  gst_pad_push (pad, gst_buffer_new ());
  fail_unless (blocked == TRUE);
  /* block_async_first flushes to unblock */
  gst_pad_push_event (pad, gst_event_new_flush_stop ());

  /* push again, this time block_async_second should be called */
  gst_pad_push (pad, gst_buffer_new ());
  fail_unless (blocked == TRUE);

  gst_object_unref (pad);
}

GST_END_TEST;
#endif

static void
block_async_full_destroy (gpointer user_data)
{
  gint *state = (gint *) user_data;

  fail_unless (*state < 2);

  GST_DEBUG ("setting state to 2");
  *state = 2;
}

static GstPadProbeReturn
block_async_full_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  *(gint *) user_data = (gint) TRUE;

  gst_pad_push_event (pad, gst_event_new_flush_start ());
  GST_DEBUG ("setting state to 1");

  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_block_async_full_destroy)
{
  GstPad *pad;
  /* 0 = unblocked, 1 = blocked, 2 = destroyed */
  gint state = 0;
  gulong id;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);
  gst_pad_set_active (pad, TRUE);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK, block_async_full_cb,
      &state, block_async_full_destroy);
  fail_unless (state == 0);

  gst_pad_push (pad, gst_buffer_new ());
  /* block_async_full_cb sets state to 1 and then flushes to unblock temporarily
   */
  fail_unless (state == 1);
  gst_pad_push_event (pad, gst_event_new_flush_stop (TRUE));

  /* unblock callback is called */
  gst_pad_remove_probe (pad, id);
  fail_unless (state == 2);

  gst_object_unref (pad);
}

GST_END_TEST;

GST_START_TEST (test_block_async_full_destroy_dispose)
{
  GstPad *pad;
  /* 0 = unblocked, 1 = blocked, 2 = destroyed */
  gint state = 0;

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);
  gst_pad_set_active (pad, TRUE);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  (void) gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK, block_async_full_cb,
      &state, block_async_full_destroy);

  gst_pad_push (pad, gst_buffer_new ());
  /* block_async_full_cb sets state to 1 and then flushes to unblock temporarily
   */
  fail_unless_equals_int (state, 1);
  gst_pad_push_event (pad, gst_event_new_flush_stop (TRUE));

  /* gst_BLOCK calls the destroy_notify function if necessary */
  gst_object_unref (pad);

  fail_unless_equals_int (state, 2);
}

GST_END_TEST;


#if 0
static void
unblock_async_no_flush_cb (GstPad * pad, gboolean blocked, gpointer user_data)
{
  gboolean *bool_user_data = (gboolean *) user_data;

  /* here we should have blocked == 1 unblocked == 0 */

  fail_unless (blocked == FALSE);

  fail_unless (bool_user_data[0] == TRUE);
  fail_unless (bool_user_data[1] == TRUE);
  fail_unless (bool_user_data[2] == FALSE);

  bool_user_data[2] = TRUE;
}
#endif


#if 0
static void
unblock_async_not_called (GstPad * pad, gboolean blocked, gpointer user_data)
{
  g_warn_if_reached ();
}
#endif

static GstPadProbeReturn
block_async_second_no_flush (GstPad * pad, GstPadProbeInfo * info,
    gpointer user_data)
{
  gboolean *bool_user_data = (gboolean *) user_data;

  GST_DEBUG ("second probe called");

  fail_unless (info->type & GST_PAD_PROBE_TYPE_BLOCK);

  fail_unless (bool_user_data[0] == TRUE);
  fail_unless (bool_user_data[1] == FALSE);
  fail_unless (bool_user_data[2] == FALSE);

  bool_user_data[1] = TRUE;

  GST_DEBUG ("removing second probe with id %lu", id);
  gst_pad_remove_probe (pad, id);

  return GST_PAD_PROBE_OK;
}

static GstPadProbeReturn
block_async_first_no_flush (GstPad * pad, GstPadProbeInfo * info,
    gpointer user_data)
{
  static int n_calls = 0;
  gboolean *bool_user_data = (gboolean *) user_data;

  fail_unless (info->type & GST_PAD_PROBE_TYPE_BLOCK);

  GST_DEBUG ("first probe called");

  if (++n_calls > 1)
    /* we expect this callback to be called only once */
    g_warn_if_reached ();

  *bool_user_data = TRUE;

  fail_unless (bool_user_data[0] == TRUE);
  fail_unless (bool_user_data[1] == FALSE);
  fail_unless (bool_user_data[2] == FALSE);

  GST_DEBUG ("removing first probe with id %lu", id);
  gst_pad_remove_probe (pad, id);

  GST_DEBUG ("adding second probe");
  /* replace block_async_first with block_async_second so next time the pad is
   * blocked the latter should be called */
  id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK,
      block_async_second_no_flush, user_data, NULL);
  GST_DEBUG ("added probe with id %lu", id);

  return GST_PAD_PROBE_OK;
}

GST_START_TEST (test_block_async_replace_callback_no_flush)
{
  GstPad *pad;
  gboolean bool_user_data[3] = { FALSE, FALSE, FALSE };

  pad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (pad != NULL);
  gst_pad_set_active (pad, TRUE);

  fail_unless (gst_pad_push_event (pad,
          gst_event_new_stream_start ("test")) == TRUE);
  fail_unless (gst_pad_push_event (pad,
          gst_event_new_segment (&dummy_segment)) == TRUE);

  GST_DEBUG ("adding probe");
  id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK,
      block_async_first_no_flush, bool_user_data, NULL);
  GST_DEBUG ("added probe with id %lu", id);
  fail_if (id == 0);

  GST_DEBUG ("pushing buffer");
  gst_pad_push (pad, gst_buffer_new ());
  fail_unless (bool_user_data[0] == TRUE);
  fail_unless (bool_user_data[1] == TRUE);
  fail_unless (bool_user_data[2] == FALSE);

  gst_object_unref (pad);
}

GST_END_TEST;

static gint sticky_count;

static gboolean
test_sticky_events_handler (GstPad * pad, GstObject * parent, GstEvent * event)
{
  GST_DEBUG_OBJECT (pad, "received event %" GST_PTR_FORMAT, event);

  switch (sticky_count) {
    case 0:
      fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START);
      break;
    case 1:
    {
      GstCaps *caps;
      GstStructure *s;

      fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_CAPS);

      gst_event_parse_caps (event, &caps);
      fail_unless (gst_caps_get_size (caps) == 1);
      s = gst_caps_get_structure (caps, 0);
      fail_unless (gst_structure_has_name (s, "foo/baz"));
      break;
    }
    case 2:
      fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT);
      break;
    case 3:
      fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_INSTANT_RATE_CHANGE);
      break;
    case 4:
      fail_unless (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_COLLECTION);
      break;
    default:
      fail_unless (FALSE);
      break;
  }

  gst_event_unref (event);
  sticky_count++;

  return TRUE;
}

static GstFlowReturn
test_sticky_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
  gst_buffer_unref (buffer);
  return GST_FLOW_OK;
}

GST_START_TEST (test_sticky_events)
{
  GstPad *srcpad, *sinkpad;
  GstCaps *caps;
  GstSegment seg;
  gchar *id;

  /* make unlinked srcpad */
  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  gst_pad_set_active (srcpad, TRUE);

  /* test stream-start */
  fail_unless (gst_pad_get_stream_id (srcpad) == NULL);

  /* push an event, it should be sticky on the srcpad */
  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_stream_start ("test")) == TRUE);

  /* let's see if it stuck */
  id = gst_pad_get_stream_id (srcpad);
  fail_unless_equals_string (id, "test");
  g_free (id);

  /* make a caps event */
  caps = gst_caps_new_empty_simple ("foo/bar");
  gst_pad_push_event (srcpad, gst_event_new_caps (caps));
  gst_caps_unref (caps);

  /* make segment event */
  gst_segment_init (&seg, GST_FORMAT_TIME);
  gst_pad_push_event (srcpad, gst_event_new_segment (&seg));

  /* Push a stream collection */
  GstStreamCollection *collection = gst_stream_collection_new (0);
  gst_pad_push_event (srcpad, gst_event_new_stream_collection (collection));
  gst_object_unref (collection);

  /* Push an instant rate change, which should be sent earlier than the preceding stream collection */
  gst_pad_push_event (srcpad, gst_event_new_instant_rate_change (1.0,
          GST_SEGMENT_FLAG_NONE));

  /* now make a sinkpad */
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);
  sticky_count = 0;
  gst_pad_set_event_function (sinkpad, test_sticky_events_handler);
  gst_pad_set_chain_function (sinkpad, test_sticky_chain);
  fail_unless (sticky_count == 0);
  gst_pad_set_active (sinkpad, TRUE);

  /* link the pads */
  gst_pad_link (srcpad, sinkpad);
  /* should not trigger events */
  fail_unless (sticky_count == 0);

  /* caps replaces old caps event at position 2, the pushes all
   * pending events */
  caps = gst_caps_new_empty_simple ("foo/baz");
  gst_pad_push_event (srcpad, gst_event_new_caps (caps));
  gst_caps_unref (caps);

  /* should have triggered 2 events, the segment, stream collection and instant-rate events are still pending */
  fail_unless_equals_int (sticky_count, 2);

  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_OK);

  /* should have triggered 5 events */
  fail_unless_equals_int (sticky_count, 5);

  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

static GstFlowReturn next_return;

static GstFlowReturn
test_lastflow_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
  gst_buffer_unref (buffer);
  return next_return;
}

GST_START_TEST (test_last_flow_return_push)
{
  GstPad *srcpad, *sinkpad;
  GstSegment seg;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);
  gst_pad_set_chain_function (sinkpad, test_lastflow_chain);
  gst_pad_link (srcpad, sinkpad);

  /* initial value is flushing */
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_FLUSHING);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_FLUSHING);

  /* when active it goes to ok */
  gst_pad_set_active (srcpad, TRUE);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);
  gst_pad_set_active (sinkpad, TRUE);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);

  /* startup events */
  gst_pad_push_event (srcpad, gst_event_new_stream_start ("test"));
  gst_segment_init (&seg, GST_FORMAT_TIME);
  gst_pad_push_event (srcpad, gst_event_new_segment (&seg));


  /* push Ok */
  next_return = GST_FLOW_OK;
  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);

  /* push not-linked */
  next_return = GST_FLOW_NOT_LINKED;
  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_NOT_LINKED);

  /* push not-linked */
  next_return = GST_FLOW_NOT_NEGOTIATED;
  fail_unless (gst_pad_push (srcpad,
          gst_buffer_new ()) == GST_FLOW_NOT_NEGOTIATED);
  fail_unless (gst_pad_get_last_flow_return (srcpad) ==
      GST_FLOW_NOT_NEGOTIATED);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) ==
      GST_FLOW_NOT_NEGOTIATED);

  /* push error */
  next_return = GST_FLOW_ERROR;
  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_ERROR);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_ERROR);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_ERROR);

  /* back to ok */
  next_return = GST_FLOW_OK;
  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);

  /* unlinked push */
  gst_pad_unlink (srcpad, sinkpad);
  fail_unless (gst_pad_push (srcpad, gst_buffer_new ()) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_NOT_LINKED);
  /* The last flow ret from the peer pad shouldn't have changed */
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);

  gst_pad_link (srcpad, sinkpad);
  fail_unless (gst_pad_push_event (srcpad, gst_event_new_eos ()));
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_EOS);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_EOS);

  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

static GstFlowReturn
test_lastflow_getrange (GstPad * pad, GstObject * parent, guint64 offset,
    guint length, GstBuffer ** buf)
{
  if (next_return == GST_FLOW_OK)
    *buf = gst_buffer_new ();
  else
    *buf = NULL;
  return next_return;
}

static gboolean
test_lastflow_activate_pull_func (GstPad * pad, GstObject * object)
{
  return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE);
}

GST_START_TEST (test_last_flow_return_pull)
{
  GstPad *srcpad, *sinkpad;
  GstBuffer *buf = NULL;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);
  gst_pad_set_getrange_function (srcpad, test_lastflow_getrange);
  gst_pad_set_activate_function (sinkpad, test_lastflow_activate_pull_func);
  gst_pad_link (srcpad, sinkpad);

  /* initial value is flushing */
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_FLUSHING);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_FLUSHING);

  /* when active it goes to ok */
  gst_pad_set_active (sinkpad, TRUE);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);
  gst_pad_set_active (srcpad, TRUE);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);

  /* pull Ok */
  next_return = GST_FLOW_OK;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);
  gst_buffer_unref (buf);
  buf = NULL;

  /* pull not-linked */
  next_return = GST_FLOW_NOT_LINKED;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_NOT_LINKED);

  /* pull error */
  next_return = GST_FLOW_ERROR;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_ERROR);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_ERROR);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_ERROR);

  /* pull not-nego */
  next_return = GST_FLOW_NOT_NEGOTIATED;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1,
          &buf) == GST_FLOW_NOT_NEGOTIATED);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) ==
      GST_FLOW_NOT_NEGOTIATED);
  fail_unless (gst_pad_get_last_flow_return (srcpad) ==
      GST_FLOW_NOT_NEGOTIATED);

  /* pull ok again */
  next_return = GST_FLOW_OK;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_OK);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);
  gst_buffer_unref (buf);
  buf = NULL;

  /* unlinked pads */
  gst_pad_unlink (srcpad, sinkpad);
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_NOT_LINKED);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_NOT_LINKED);
  /* Return value for the remote pad didn't change */
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_OK);

  /* eos */
  gst_pad_link (srcpad, sinkpad);
  next_return = GST_FLOW_EOS;
  fail_unless (gst_pad_pull_range (sinkpad, 0, 1, &buf) == GST_FLOW_EOS);
  fail_unless (gst_pad_get_last_flow_return (sinkpad) == GST_FLOW_EOS);
  fail_unless (gst_pad_get_last_flow_return (srcpad) == GST_FLOW_EOS);

  gst_object_unref (srcpad);
  gst_object_unref (sinkpad);
}

GST_END_TEST;

GST_START_TEST (test_flush_stop_inactive)
{
  GstPad *sinkpad, *srcpad;

  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);
  fail_unless (sinkpad != NULL);

  /* new pads are inactive and flushing */
  fail_if (GST_PAD_IS_ACTIVE (sinkpad));
  fail_unless (GST_PAD_IS_FLUSHING (sinkpad));

  /* this should fail, pad is inactive */
  fail_if (gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE)));

  /* nothing should have changed */
  fail_if (GST_PAD_IS_ACTIVE (sinkpad));
  fail_unless (GST_PAD_IS_FLUSHING (sinkpad));

  gst_pad_set_active (sinkpad, TRUE);

  /* pad is now active an not flushing anymore */
  fail_unless (GST_PAD_IS_ACTIVE (sinkpad));
  fail_if (GST_PAD_IS_FLUSHING (sinkpad));

  /* do flush, does not deactivate the pad */
  fail_unless (gst_pad_send_event (sinkpad, gst_event_new_flush_start ()));
  fail_unless (GST_PAD_IS_ACTIVE (sinkpad));
  fail_unless (GST_PAD_IS_FLUSHING (sinkpad));

  fail_unless (gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE)));
  fail_unless (GST_PAD_IS_ACTIVE (sinkpad));
  fail_if (GST_PAD_IS_FLUSHING (sinkpad));

  gst_pad_set_active (sinkpad, FALSE);
  fail_if (GST_PAD_IS_ACTIVE (sinkpad));
  fail_unless (GST_PAD_IS_FLUSHING (sinkpad));

  gst_object_unref (sinkpad);

  /* we should not be able to push on an inactive srcpad */
  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);

  fail_if (GST_PAD_IS_ACTIVE (srcpad));
  fail_unless (GST_PAD_IS_FLUSHING (srcpad));

  fail_if (gst_pad_push_event (srcpad, gst_event_new_flush_stop (FALSE)));

  /* should still be inactive and flushing */
  fail_if (GST_PAD_IS_ACTIVE (srcpad));
  fail_unless (GST_PAD_IS_FLUSHING (srcpad));

  gst_pad_set_active (srcpad, TRUE);

  /* pad is now active an not flushing anymore */
  fail_unless (GST_PAD_IS_ACTIVE (srcpad));
  fail_if (GST_PAD_IS_FLUSHING (srcpad));

  /* do flush, does not deactivate the pad */
  fail_if (gst_pad_push_event (srcpad, gst_event_new_flush_start ()));
  fail_unless (GST_PAD_IS_ACTIVE (srcpad));
  fail_unless (GST_PAD_IS_FLUSHING (srcpad));

  fail_if (gst_pad_push_event (srcpad, gst_event_new_flush_stop (FALSE)));
  fail_unless (GST_PAD_IS_ACTIVE (srcpad));
  fail_if (GST_PAD_IS_FLUSHING (srcpad));

  gst_pad_set_active (srcpad, FALSE);
  fail_if (GST_PAD_IS_ACTIVE (srcpad));
  fail_unless (GST_PAD_IS_FLUSHING (srcpad));

  gst_object_unref (srcpad);
}

GST_END_TEST;

/* For proxy caps flag tests */

typedef struct _GstProxyTestElement GstProxyTestElement;
typedef struct _GstProxyTestElementClass GstProxyTestElementClass;

struct _GstProxyTestElement
{
  GstElement element;
};

struct _GstProxyTestElementClass
{
  GstElementClass parent_class;
};

G_GNUC_INTERNAL GType gst_proxytestelement_get_type (void);

static GstStaticPadTemplate proxytestelement_peer_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("test/proxy, option=(int)1"));

static GstStaticPadTemplate proxytestelement_peer_incompatible_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("test/proxy-incompatible"));

static GstStaticPadTemplate proxytestelement_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("test/proxy"));

static GstStaticPadTemplate proxytestelement_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

G_DEFINE_TYPE (GstProxyTestElement, gst_proxytestelement, GST_TYPE_ELEMENT);

static void
gst_proxytestelement_class_init (GstProxyTestElementClass * klass)
{
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_set_static_metadata (gstelement_class,
      "Proxy Test Element", "Test", "Proxy test element",
      "Thiago Santos <thiagoss@osg.samsung.com>");

  gst_element_class_add_static_pad_template (gstelement_class,
      &proxytestelement_sink_template);
}

static void
gst_proxytestelement_init (GstProxyTestElement * element)
{
  GstPad *sinkpad;
  sinkpad =
      gst_pad_new_from_static_template (&proxytestelement_sink_template,
      "sink");
  GST_PAD_SET_PROXY_CAPS (sinkpad);
  gst_element_add_pad (GST_ELEMENT_CAST (element), sinkpad);
}

GST_START_TEST (test_proxy_accept_caps_no_proxy)
{
  GstElement *element;
  GstPad *sinkpad;
  GstCaps *caps;

  gst_element_register (NULL, "proxytestelement", GST_RANK_NONE,
      gst_proxytestelement_get_type ());
  element = gst_element_factory_make ("proxytestelement", NULL);
  sinkpad = gst_element_get_static_pad (element, "sink");

  gst_element_set_state (element, GST_STATE_PLAYING);

  caps = gst_caps_from_string ("test/proxy");
  fail_unless (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  caps = gst_caps_from_string ("test/bad");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  gst_object_unref (sinkpad);
  gst_element_set_state (element, GST_STATE_NULL);
  gst_object_unref (element);
}

GST_END_TEST;


GST_START_TEST (test_proxy_accept_caps_with_proxy)
{
  GstElement *element;
  GstPad *sinkpad, *srcpad;
  GstPad *peerpad;
  GstCaps *caps;

  gst_element_register (NULL, "proxytestelement", GST_RANK_NONE,
      gst_proxytestelement_get_type ());
  element = gst_element_factory_make ("proxytestelement", NULL);

  srcpad =
      gst_pad_new_from_static_template (&proxytestelement_src_template, "src");
  gst_element_add_pad (GST_ELEMENT_CAST (element), srcpad);

  sinkpad = gst_element_get_static_pad (element, "sink");
  srcpad = gst_element_get_static_pad (element, "src");

  peerpad =
      gst_pad_new_from_static_template (&proxytestelement_peer_template,
      "sink");
  fail_unless (gst_pad_link (srcpad, peerpad) == GST_PAD_LINK_OK);
  gst_pad_set_active (peerpad, TRUE);

  gst_element_set_state (element, GST_STATE_PLAYING);

  caps = gst_caps_from_string ("test/bad");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  caps = gst_caps_from_string ("test/proxy, option=(int)1");
  fail_unless (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  caps = gst_caps_from_string ("test/proxy, option=(int)2");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  gst_object_unref (sinkpad);
  gst_object_unref (srcpad);
  gst_pad_set_active (peerpad, FALSE);
  gst_object_unref (peerpad);
  gst_element_set_state (element, GST_STATE_NULL);
  gst_object_unref (element);
}

GST_END_TEST;

GST_START_TEST (test_proxy_accept_caps_with_incompatible_proxy)
{
  GstElement *element;
  GstPad *sinkpad, *srcpad;
  GstPad *peerpad;
  GstCaps *caps;

  gst_element_register (NULL, "proxytestelement", GST_RANK_NONE,
      gst_proxytestelement_get_type ());
  element = gst_element_factory_make ("proxytestelement", NULL);

  srcpad =
      gst_pad_new_from_static_template (&proxytestelement_src_template, "src");
  gst_element_add_pad (GST_ELEMENT_CAST (element), srcpad);

  sinkpad = gst_element_get_static_pad (element, "sink");
  srcpad = gst_element_get_static_pad (element, "src");

  peerpad =
      gst_pad_new_from_static_template
      (&proxytestelement_peer_incompatible_template, "sink");
  fail_unless (gst_pad_link (srcpad, peerpad) == GST_PAD_LINK_OK);

  gst_element_set_state (element, GST_STATE_PLAYING);

  caps = gst_caps_from_string ("test/bad");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  caps = gst_caps_from_string ("test/proxy");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  caps = gst_caps_from_string ("test/proxy-incompatible");
  fail_if (gst_pad_query_accept_caps (sinkpad, caps));
  gst_caps_unref (caps);

  gst_object_unref (sinkpad);
  gst_object_unref (srcpad);
  gst_pad_set_active (peerpad, FALSE);
  gst_object_unref (peerpad);
  gst_element_set_state (element, GST_STATE_NULL);
  gst_object_unref (element);
}

GST_END_TEST;

static GstSegment sink_segment;
static gint sink_segment_counter;

static gboolean
segment_event_func (GstPad * pad, GstObject * parent, GstEvent * event)
{
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEGMENT:
      gst_event_copy_segment (event, &sink_segment);
      sink_segment_counter++;
      break;
    default:
      break;
  }

  gst_event_unref (event);
  return TRUE;
}

static void
test_pad_offset (gboolean on_srcpad)
{
  GstPad *srcpad, *sinkpad, *offset_pad;
  GstSegment segment;
  GstBuffer *buffer;
  GstQuery *query;

  srcpad = gst_pad_new ("src", GST_PAD_SRC);
  fail_unless (srcpad != NULL);
  sinkpad = gst_pad_new ("sink", GST_PAD_SINK);

  offset_pad = on_srcpad ? srcpad : sinkpad;

  gst_segment_init (&sink_segment, GST_FORMAT_UNDEFINED);
  sink_segment_counter = 0;
  gst_pad_set_chain_function (sinkpad, gst_check_chain_func);
  gst_pad_set_event_function (sinkpad, segment_event_func);

  fail_unless (sinkpad != NULL);
  fail_unless_equals_int (gst_pad_link (srcpad, sinkpad), GST_PAD_LINK_OK);
  fail_unless (gst_pad_set_active (sinkpad, TRUE));
  fail_unless (gst_pad_set_active (srcpad, TRUE));

  /* Set an offset of 5s, meaning:
   * segment position 0 gives running time 5s, stream time 0s
   * segment start of 0 should stay 0
   */
  gst_pad_set_offset (offset_pad, 5 * GST_SECOND);

  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_stream_start ("test")) == TRUE);
  /* We should have no segment event yet */
  fail_if (sink_segment.format != GST_FORMAT_UNDEFINED);
  fail_unless_equals_int (sink_segment_counter, 0);

  /* Send segment event, expect it to arrive with a modified start running time */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_segment (&segment)) == TRUE);
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 1);

  /* Send a buffer and check if all timestamps are as expected, and especially
   * if the buffer timestamp was not changed */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = 0 * GST_SECOND;
  fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK);

  fail_unless_equals_int (g_list_length (buffers), 1);
  buffer = buffers->data;
  buffers = g_list_delete_link (buffers, buffers);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 0 * GST_SECOND);
  fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer), 0 * GST_SECOND);
  gst_buffer_unref (buffer);

  fail_unless_equals_int (sink_segment_counter, 1);

  /* Set a negative offset of -5s, meaning:
   * segment position 5s gives running time 0s, stream time 5s
   * segment start would have a negative running time!
   */
  gst_pad_set_offset (offset_pad, -5 * GST_SECOND);

  /* Segment should still be the same as before */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 1);

  /* Send segment event, expect it to arrive with a modified start running time */
  gst_segment_init (&segment, GST_FORMAT_TIME);
  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_segment (&segment)) == TRUE);
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start + 5 * GST_SECOND),
      0 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start + 5 * GST_SECOND),
      5 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 2);

  /* Send a buffer and check if all timestamps are as expected, and especially
   * if the buffer timestamp was not changed */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = 5 * GST_SECOND;
  fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK);

  fail_unless_equals_int (g_list_length (buffers), 1);
  buffer = buffers->data;
  buffers = g_list_delete_link (buffers, buffers);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 0 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 5 * GST_SECOND);
  fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer), 5 * GST_SECOND);
  gst_buffer_unref (buffer);

  fail_unless_equals_int (sink_segment_counter, 2);

  /* Set offset to 5s again, same situation as above but don't send a new
   * segment event. The segment should be adjusted *before* the buffer comes
   * out of the srcpad */
  gst_pad_set_offset (offset_pad, 5 * GST_SECOND);

  /* Segment should still be the same as before */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start + 5 * GST_SECOND),
      0 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start + 5 * GST_SECOND),
      5 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 2);

  /* Send a buffer and check if a new segment event was sent and all buffer
   * timestamps are as expected */
  buffer = gst_buffer_new ();
  GST_BUFFER_PTS (buffer) = 0 * GST_SECOND;
  fail_unless_equals_int (gst_pad_push (srcpad, buffer), GST_FLOW_OK);

  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 3);

  fail_unless_equals_int (g_list_length (buffers), 1);
  buffer = buffers->data;
  buffers = g_list_delete_link (buffers, buffers);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, GST_BUFFER_PTS (buffer)), 0 * GST_SECOND);
  fail_unless_equals_uint64 (GST_BUFFER_PTS (buffer), 0 * GST_SECOND);
  gst_buffer_unref (buffer);

  fail_unless_equals_int (sink_segment_counter, 3);

  /* Set offset to 10s and send another sticky event. In between a new
   * segment event should've been sent */
  gst_pad_set_offset (offset_pad, 10 * GST_SECOND);

  /* Segment should still be the same as before */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 5 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);
  fail_unless_equals_int (sink_segment_counter, 3);

  fail_unless (gst_pad_push_event (srcpad,
          gst_event_new_tag (gst_tag_list_new_empty ())) == TRUE);

  /* Segment should be updated */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 10 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 4);

  /* Set offset to 15s and do a serialized query. In between a new
   * segment event should've been sent */
  gst_pad_set_offset (offset_pad, 15 * GST_SECOND);

  /* Segment should still be the same as before */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 10 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);
  fail_unless_equals_int (sink_segment_counter, 4);

  query = gst_query_new_drain ();
  gst_pad_peer_query (srcpad, query);
  gst_query_unref (query);

  /* Segment should be updated */
  fail_if (sink_segment.format == GST_FORMAT_UNDEFINED);
  fail_unless_equals_uint64 (gst_segment_to_running_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 15 * GST_SECOND);
  fail_unless_equals_uint64 (gst_segment_to_stream_time (&sink_segment,
          GST_FORMAT_TIME, sink_segment.start), 0 * GST_SECOND);
  fail_unless_equals_uint64 (sink_segment.start, 0 * GST_SECOND);

  fail_unless_equals_int (sink_segment_counter, 5);

  gst_check_drop_buffers ();

  fail_unless (gst_pad_set_active (sinkpad, FALSE));
  fail_unless (gst_pad_set_active (srcpad, FALSE));
  gst_object_unref (sinkpad);
  gst_object_unref (srcpad);
}

GST_START_TEST (test_pad_offset_src)
{
  test_pad_offset (TRUE);
}

GST_END_TEST;

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

  /* turn off timeout */
  tcase_set_timeout (tc_chain, 60);

  gst_segment_init (&dummy_segment, GST_FORMAT_BYTES);

  suite_add_tcase (s, tc_chain);
  tcase_add_test (tc_chain, test_link);
  tcase_add_test (tc_chain, test_refcount);
  tcase_add_test (tc_chain, test_get_allowed_caps);
  tcase_add_test (tc_chain, test_sticky_caps_unlinked);
  tcase_add_test (tc_chain, test_sticky_caps_unlinked_incompatible);
  tcase_add_test (tc_chain, test_sticky_caps_flushing);
  tcase_add_test (tc_chain, test_default_accept_caps);
  tcase_add_test (tc_chain, test_link_unlink_threaded);
  tcase_add_test (tc_chain, test_name_is_valid);
  tcase_add_test (tc_chain, test_push_unlinked);
  tcase_add_test (tc_chain, test_push_linked);
  tcase_add_test (tc_chain, test_push_linked_flushing);
  tcase_add_test (tc_chain, test_push_buffer_list_compat);
  tcase_add_test (tc_chain, test_flowreturn);
  tcase_add_test (tc_chain, test_push_negotiation);
  tcase_add_test (tc_chain, test_src_unref_unlink);
  tcase_add_test (tc_chain, test_sink_unref_unlink);
  tcase_add_test (tc_chain, test_block_async);
  tcase_add_test (tc_chain, test_pad_blocking_with_probe_type_block);
  tcase_add_test (tc_chain, test_pad_blocking_with_probe_type_blocking);
  tcase_add_test (tc_chain, test_pad_blocking_with_probe_type_idle);
  tcase_add_test (tc_chain, test_pad_probe_pull);
  tcase_add_test (tc_chain, test_pad_probe_pull_idle);
  tcase_add_test (tc_chain, test_pad_probe_pull_buffer);
  tcase_add_test (tc_chain, test_pad_probe_remove);
  tcase_add_test (tc_chain, test_pad_disjoint_blocks_probe_remove);
  tcase_add_test (tc_chain, test_pad_probe_block_add_remove);
  tcase_add_test (tc_chain, test_pad_probe_block_and_drop_buffer);
  tcase_add_test (tc_chain, test_pad_probe_flush_events);
  tcase_add_test (tc_chain, test_pad_probe_flush_events_only);
  tcase_add_test (tc_chain, test_pad_probe_call_order);
  tcase_add_test (tc_chain, test_pad_probe_handled_and_drop);
  tcase_add_test (tc_chain, test_events_query_unlinked);
  tcase_add_test (tc_chain, test_queue_src_caps_notify_linked);
  tcase_add_test (tc_chain, test_queue_src_caps_notify_not_linked);
#if 0
  tcase_add_test (tc_chain, test_block_async_replace_callback);
#endif
  tcase_add_test (tc_chain, test_block_async_full_destroy);
  tcase_add_test (tc_chain, test_block_async_full_destroy_dispose);
  tcase_add_test (tc_chain, test_block_async_replace_callback_no_flush);
  tcase_add_test (tc_chain, test_sticky_events);
  tcase_add_test (tc_chain, test_last_flow_return_push);
  tcase_add_test (tc_chain, test_last_flow_return_pull);
  tcase_add_test (tc_chain, test_flush_stop_inactive);
  tcase_add_test (tc_chain, test_proxy_accept_caps_no_proxy);
  tcase_add_test (tc_chain, test_proxy_accept_caps_with_proxy);
  tcase_add_test (tc_chain, test_proxy_accept_caps_with_incompatible_proxy);
  tcase_add_test (tc_chain, test_pad_offset_src);

  return s;
}

GST_CHECK_MAIN (gst_pad);
