Rework GstSegment handling
[platform/upstream/gstreamer.git] / tests / check / generic / sinks.c
index 7cf5bea..4482473 100644 (file)
@@ -136,8 +136,8 @@ GST_START_TEST (test_src_sink)
   gst_bin_add (GST_BIN (pipeline), src);
   gst_bin_add (GST_BIN (pipeline), sink);
 
-  srcpad = gst_element_get_pad (src, "src");
-  sinkpad = gst_element_get_pad (sink, "sink");
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
   gst_pad_link (srcpad, sinkpad);
   gst_object_unref (srcpad);
   gst_object_unref (sinkpad);
@@ -182,8 +182,8 @@ GST_START_TEST (test_livesrc_remove)
   gst_bin_add (GST_BIN (pipeline), src);
   gst_bin_add (GST_BIN (pipeline), sink);
 
-  srcpad = gst_element_get_pad (src, "src");
-  sinkpad = gst_element_get_pad (sink, "sink");
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
   gst_pad_link (srcpad, sinkpad);
   gst_object_unref (srcpad);
   gst_object_unref (sinkpad);
@@ -235,8 +235,8 @@ GST_START_TEST (test_livesrc_sink)
   gst_bin_add (GST_BIN (pipeline), src);
   gst_bin_add (GST_BIN (pipeline), sink);
 
-  srcpad = gst_element_get_pad (src, "src");
-  sinkpad = gst_element_get_pad (sink, "sink");
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
   gst_pad_link (srcpad, sinkpad);
   gst_object_unref (srcpad);
   gst_object_unref (sinkpad);
@@ -313,7 +313,7 @@ GST_START_TEST (test_livesrc_sink)
         if (n_sink == 2) {
           fail_unless (old == GST_STATE_READY, "unexpected old");
           fail_unless (new == GST_STATE_PAUSED, "unexpected new");
-          fail_unless (pending == GST_STATE_PLAYING, "unexpected pending");
+          fail_unless (pending == GST_STATE_VOID_PENDING, "unexpected pending");
         } else if (n_sink == 1) {
           fail_unless (old == GST_STATE_PAUSED, "unexpected old");
           fail_unless (new == GST_STATE_PLAYING, "unexpected new");
@@ -358,8 +358,8 @@ GST_START_TEST (test_livesrc2_sink)
   gst_bin_add (GST_BIN (pipeline), src);
   gst_bin_add (GST_BIN (pipeline), sink);
 
-  srcpad = gst_element_get_pad (src, "src");
-  sinkpad = gst_element_get_pad (sink, "sink");
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
   gst_pad_link (srcpad, sinkpad);
   gst_object_unref (srcpad);
   gst_object_unref (sinkpad);
@@ -427,8 +427,8 @@ GST_START_TEST (test_livesrc3_sink)
   gst_bin_add (GST_BIN (pipeline), src);
   gst_bin_add (GST_BIN (pipeline), sink);
 
-  srcpad = gst_element_get_pad (src, "src");
-  sinkpad = gst_element_get_pad (sink, "sink");
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
   gst_pad_link (srcpad, sinkpad);
   gst_object_unref (srcpad);
   gst_object_unref (sinkpad);
@@ -450,12 +450,963 @@ GST_START_TEST (test_livesrc3_sink)
 
 GST_END_TEST;
 
+GST_START_TEST (test_locked_sink)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add (GST_BIN (pipeline), src);
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we don't link the elements */
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* the sink is now async going from ready to paused */
+  ret = gst_element_get_state (sink, &current, &pending, 0);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not async");
+  fail_unless (current == GST_STATE_READY, "not ready");
+  fail_unless (pending == GST_STATE_PAUSED, "not paused");
+
+  /* lock the sink */
+  gst_element_set_locked_state (sink, TRUE);
+
+  /* move to PlAYING, the sink should remain ASYNC. The pipeline
+   * returns ASYNC */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* back to PAUSED, we should get NO_PREROLL again */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* unlock the sink */
+  gst_element_set_locked_state (sink, FALSE);
+
+  /* and now everything back down */
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_unlinked_live)
+{
+  GstElement *sink, *src, *lsrc, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  lsrc = gst_element_factory_make ("fakesrc", "lsrc");
+  g_object_set (G_OBJECT (lsrc), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add (GST_BIN (pipeline), src);
+  gst_bin_add (GST_BIN (pipeline), lsrc);
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* link non live source to sink */
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  /* we don't link the srcpad of the live source, it will not contribute to the
+   * NO_PREROLL. */
+
+  /* set state to PAUSED, this should return NO_PREROLL because there is a live
+   * source. since the only sink in this pipeline is linked to a non-live
+   * source, it will preroll eventually. */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* wait till the sink is prerolled */
+  ret = gst_element_get_state (sink, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not success");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
+
+  /* the pipeline should still return NO_PREROLL */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_delayed_async)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add sink now and set to PAUSED */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* This will make the bin notice an ASYNC element. */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should still be NO_PREROLL now although there is an async element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* link live source to sink */
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should get SUCCESS now */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_added_async)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* set sink to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* add sink now, pipeline should notice the async element */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we should still be NO_PREROLL now although there is an async element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* link live source to sink */
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should get SUCCESS now */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_added_async2)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+  /* need to lock state here or the pipeline might go in error */
+  gst_element_set_locked_state (src, TRUE);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+
+  /* set sink to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* add sink now, pipeline should notice the async element */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we should be ASYNC now because there is an async element in the
+   * pipeline. */
+  ret = gst_element_get_state (pipeline, &current, &pending, 0);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not ASYNC");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_PAUSED, "not paused");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_live)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add sink, don't add sourc3 yet */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* set source to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (src, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add source now, pipeline should notice the NO_PREROLL element */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  /* we should be NO_PREROLL now because there is a NO_PREROLL element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+static GMutex *blocked_lock;
+static GCond *blocked_cond;
+
+static void
+pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data)
+{
+  g_mutex_lock (blocked_lock);
+  GST_DEBUG ("srcpad blocked: %d, sending signal", blocked);
+  g_cond_signal (blocked_cond);
+  g_mutex_unlock (blocked_lock);
+}
+
+GST_START_TEST (test_add_live2)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  blocked_lock = g_mutex_new ();
+  blocked_cond = g_cond_new ();
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add sink, don't add source yet */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* set the pipeline to PLAYING. This will return ASYNC on READY->PAUSED */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  g_mutex_lock (blocked_lock);
+
+  GST_DEBUG ("blocking srcpad");
+  /* block source pad */
+  srcpad = gst_element_get_static_pad (src, "src");
+  gst_pad_set_blocked_async (srcpad, TRUE, pad_blocked_cb, NULL);
+
+  /* set source to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (src, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add source now, pipeline should notice the NO_PREROLL element. This
+   * should trigger as commit of the ASYNC pipeline and make it continue
+   * to PLAYING. We blocked the source pad so that we don't get an unlinked
+   * error. */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  /* wait for pad blocked, this means the source is now PLAYING. */
+  g_cond_wait (blocked_cond, blocked_lock);
+  g_mutex_unlock (blocked_lock);
+
+  GST_DEBUG ("linking pads");
+
+  /* link to sink */
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  GST_DEBUG ("unblocking srcpad");
+
+  /* and unblock */
+  gst_pad_set_blocked_async (srcpad, FALSE, pad_blocked_cb, NULL);
+
+  GST_DEBUG ("getting state");
+
+  /* we should be SUCCESS now and PLAYING */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  g_cond_free (blocked_cond);
+  g_mutex_free (blocked_lock);
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_bin_live)
+{
+  GstElement *sink, *src, *pipeline, *bin;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  bin = gst_bin_new ("bin");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add (GST_BIN (bin), src);
+  gst_bin_add (GST_BIN (bin), sink);
+  gst_bin_add (GST_BIN (pipeline), bin);
+
+  srcpad = gst_element_get_static_pad (src, "src");
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  /* PAUSED returns NO_PREROLL because of the live source */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "not void pending");
+
+  /* when going to PLAYING, the sink should go to PLAYING ASYNC */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not ASYNC");
+
+  /* now wait for PLAYING to complete */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not playing");
+  fail_unless (current == GST_STATE_PLAYING, "not playing");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "not void pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "cannot null pipeline");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST static void
+send_eos (GstPad * sinkpad)
+{
+  gst_pad_send_event (sinkpad, gst_event_new_eos ());
+}
+
+/* push a buffer with a very long duration in a fakesink, then push an EOS
+ * event. fakesink should emit EOS after the duration of the buffer expired.
+ * Going to PAUSED, however should not return ASYNC while processing the
+ * buffer. */
+GST_START_TEST (test_fake_eos)
+{
+  GstElement *sink, *pipeline;
+  GstBuffer *buffer;
+  GstStateChangeReturn ret;
+  GstPad *sinkpad;
+  GstFlowReturn res;
+  GThread *thread;
+
+  pipeline = gst_pipeline_new ("pipeline");
+
+  sink = gst_element_factory_make ("fakesink", "sink");
+  g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+
+  gst_bin_add (GST_BIN_CAST (pipeline), sink);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* push buffer of 100 seconds, since it has a timestamp of 0, it should be
+   * rendered immediatly and the chain function should return immediatly */
+  buffer = gst_buffer_new_and_alloc (10);
+  GST_BUFFER_TIMESTAMP (buffer) = 0;
+  GST_BUFFER_DURATION (buffer) = 100 * GST_SECOND;
+  res = gst_pad_chain (sinkpad, buffer);
+  fail_unless (res == GST_FLOW_OK, "no OK flow return");
+
+  /* wait for preroll, this should happen really soon. */
+  ret = gst_element_get_state (pipeline, NULL, NULL, -1);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+
+  /* push EOS, this will block for up to 100 seconds, until the previous
+   * buffer has finished. We therefore push it in another thread so we can do
+   * something else while it blocks. */
+  thread = g_thread_create ((GThreadFunc) send_eos, sinkpad, TRUE, NULL);
+  fail_if (thread == NULL, "no thread");
+
+  /* wait a while so that the thread manages to start and push the EOS */
+  g_usleep (G_USEC_PER_SEC);
+
+  /* this should cancel rendering of the EOS event and should return SUCCESS
+   * because the sink is now prerolled on the EOS. */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+
+  /* we can unref the sinkpad now, we're done with it */
+  gst_object_unref (sinkpad);
+
+  /* wait for a second, use the debug log to see that basesink does not discard
+   * the EOS */
+  g_usleep (G_USEC_PER_SEC);
+
+  /* go back to PLAYING, which means waiting some more in EOS, check debug log
+   * to see this happen. */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+  g_usleep (G_USEC_PER_SEC);
+
+  /* teardown and cleanup */
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+/* this variable is updated in the same thread, first it is set by the
+ * handoff-preroll signal, then it is checked when the ASYNC_DONE is posted on
+ * the bus */
+static gboolean have_preroll = FALSE;
+
+static void
+async_done_handoff (GstElement * element, GstBuffer * buf, GstPad * pad,
+    GstElement * sink)
+{
+  GST_DEBUG ("we have the preroll buffer");
+  have_preroll = TRUE;
+}
+
+/* when we get the ASYNC_DONE, query the position */
+static GstBusSyncReply
+async_done_func (GstBus * bus, GstMessage * msg, GstElement * sink)
+{
+  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) {
+    GstFormat format;
+    gint64 position;
+
+    GST_DEBUG ("we have ASYNC_DONE now");
+    fail_unless (have_preroll == TRUE, "no preroll buffer received");
+
+    /* get the position now */
+    format = GST_FORMAT_TIME;
+    gst_element_query_position (sink, &format, &position);
+
+    GST_DEBUG ("we have position %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
+
+    fail_unless (position == 10 * GST_SECOND, "position is wrong");
+  }
+
+  /* we must unref the message if we return DROP */
+  gst_message_unref (msg);
+
+  /* we can drop the message, nothing is listening for it. */
+  return GST_BUS_DROP;
+}
+
+static void
+send_buffer (GstPad * sinkpad)
+{
+  GstBuffer *buffer;
+  GstFlowReturn ret;
+
+  /* push a second buffer */
+  GST_DEBUG ("pushing last buffer");
+  buffer = gst_buffer_new_and_alloc (10);
+  GST_BUFFER_TIMESTAMP (buffer) = 200 * GST_SECOND;
+  GST_BUFFER_DURATION (buffer) = 100 * GST_SECOND;
+
+  /* this function will initially block */
+  ret = gst_pad_chain (sinkpad, buffer);
+  fail_unless (ret == GST_FLOW_OK, "no OK flow return");
+}
+
+/* when we get the ASYNC_DONE message from a sink, we want the sink to be able
+ * to report the duration and position. The sink should also have called the
+ * render method. */
+GST_START_TEST (test_async_done)
+{
+  GstElement *sink;
+  GstBuffer *buffer;
+  GstEvent *event;
+  GstStateChangeReturn ret;
+  GstPad *sinkpad;
+  GstFlowReturn res;
+  GstBus *bus;
+  GThread *thread;
+  GstFormat format;
+  gint64 position;
+  gboolean qret;
+  GstSegment segment;
+
+  sink = gst_element_factory_make ("fakesink", "sink");
+  g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+  g_object_set (G_OBJECT (sink), "preroll-queue-len", 2, NULL);
+  g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
+
+  g_signal_connect (sink, "preroll-handoff", (GCallback) async_done_handoff,
+      sink);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* set bus on element synchronously listen for ASYNC_DONE */
+  bus = gst_bus_new ();
+  gst_element_set_bus (sink, bus);
+  gst_bus_set_sync_handler (bus, (GstBusSyncHandler) async_done_func, sink);
+
+  /* make newsegment, this sets the position to 10sec when the buffer prerolls */
+  GST_DEBUG ("sending segment");
+  gst_segment_init (&segment, GST_FORMAT_TIME);
+  segment.time = 10 * GST_SECOND;
+
+  event = gst_event_new_segment (&segment);
+  res = gst_pad_send_event (sinkpad, event);
+
+  /* We have not yet received any buffers so we are still in the READY state,
+   * the position is therefore still not queryable. */
+  format = GST_FORMAT_TIME;
+  position = -1;
+  qret = gst_element_query_position (sink, &format, &position);
+  fail_unless (qret == TRUE, "position wrong");
+  fail_unless (position == 10 * GST_SECOND, "position is wrong");
+
+  /* Since we are paused and the preroll queue has a length of 2, this function
+   * will return immediatly, the preroll handoff will be called and the stream
+   * position should now be 10 seconds. */
+  GST_DEBUG ("pushing first buffer");
+  buffer = gst_buffer_new_and_alloc (10);
+  GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+  GST_BUFFER_DURATION (buffer) = 100 * GST_SECOND;
+  res = gst_pad_chain (sinkpad, buffer);
+  fail_unless (res == GST_FLOW_OK, "no OK flow return");
+
+  /* scond buffer, will not block either but position should still be 10
+   * seconds */
+  GST_DEBUG ("pushing second buffer");
+  buffer = gst_buffer_new_and_alloc (10);
+  GST_BUFFER_TIMESTAMP (buffer) = 100 * GST_SECOND;
+  GST_BUFFER_DURATION (buffer) = 100 * GST_SECOND;
+  res = gst_pad_chain (sinkpad, buffer);
+  fail_unless (res == GST_FLOW_OK, "no OK flow return");
+
+  /* check if position is still 10 seconds */
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (sink, &format, &position);
+  GST_DEBUG ("first buffer position %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (position));
+  fail_unless (position == 10 * GST_SECOND, "position is wrong");
+
+  /* last buffer, blocks because preroll queue is filled. Start the push in a
+   * new thread so that we can check the position */
+  GST_DEBUG ("starting thread");
+  thread = g_thread_create ((GThreadFunc) send_buffer, sinkpad, TRUE, NULL);
+  fail_if (thread == NULL, "no thread");
+
+  GST_DEBUG ("waiting 1 second");
+  g_usleep (G_USEC_PER_SEC);
+  GST_DEBUG ("waiting done");
+
+  /* check if position is still 10 seconds. This is racy because  the above
+   * thread might not yet have started the push, because of the above sleep,
+   * this is very unlikely, though. */
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (sink, &format, &position);
+  GST_DEBUG ("second buffer position %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (position));
+  fail_unless (position == 10 * GST_SECOND, "position is wrong");
+
+  /* now we go to playing. This should unlock and stop the above thread. */
+  GST_DEBUG ("going to PLAYING");
+  ret = gst_element_set_state (sink, GST_STATE_PLAYING);
+
+  /* join the thread. At this point we know the sink processed the last buffer
+   * and the position should now be 210 seconds; the time of the last buffer we
+   * pushed */
+  GST_DEBUG ("joining thread");
+  g_thread_join (thread);
+
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (sink, &format, &position);
+  GST_DEBUG ("last buffer position %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (position));
+  fail_unless (position == 310 * GST_SECOND, "position is wrong");
+
+  gst_object_unref (sinkpad);
+
+  gst_element_set_state (sink, GST_STATE_NULL);
+  gst_object_unref (sink);
+  gst_object_unref (bus);
+}
+
+GST_END_TEST;
+
+/* when we get the ASYNC_DONE, query the position */
+static GstBusSyncReply
+async_done_eos_func (GstBus * bus, GstMessage * msg, GstElement * sink)
+{
+  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ASYNC_DONE) {
+    GstFormat format;
+    gint64 position;
+
+    GST_DEBUG ("we have ASYNC_DONE now");
+
+    /* get the position now */
+    format = GST_FORMAT_TIME;
+    gst_element_query_position (sink, &format, &position);
+
+    GST_DEBUG ("we have position %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
+
+    fail_unless (position == 10 * GST_SECOND, "position is wrong");
+  }
+  /* we must unref the message if we return DROP */
+  gst_message_unref (msg);
+  /* we can drop the message, nothing is listening for it. */
+  return GST_BUS_DROP;
+}
+
+/* when we get the ASYNC_DONE message from a sink, we want the sink to be able
+ * to report the duration and position. The sink should also have called the
+ * render method. */
+GST_START_TEST (test_async_done_eos)
+{
+  GstElement *sink;
+  GstEvent *event;
+  GstStateChangeReturn ret;
+  GstPad *sinkpad;
+  gboolean res;
+  GstBus *bus;
+  GstFormat format;
+  gint64 position;
+  gboolean qret;
+  GstSegment segment;
+
+  sink = gst_element_factory_make ("fakesink", "sink");
+  g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+  g_object_set (G_OBJECT (sink), "preroll-queue-len", 1, NULL);
+
+  sinkpad = gst_element_get_static_pad (sink, "sink");
+
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* set bus on element synchronously listen for ASYNC_DONE */
+  bus = gst_bus_new ();
+  gst_element_set_bus (sink, bus);
+  gst_bus_set_sync_handler (bus, (GstBusSyncHandler) async_done_eos_func, sink);
+
+  /* make newsegment, this sets the position to 10sec when the buffer prerolls */
+  GST_DEBUG ("sending segment");
+  gst_segment_init (&segment, GST_FORMAT_TIME);
+  segment.time = 10 * GST_SECOND;
+  event = gst_event_new_segment (&segment);
+  res = gst_pad_send_event (sinkpad, event);
+
+  /* We have not yet received any buffers so we are still in the READY state,
+   * the position is therefore still not queryable. */
+  format = GST_FORMAT_TIME;
+  position = -1;
+  qret = gst_element_query_position (sink, &format, &position);
+  fail_unless (qret == TRUE, "position wrong");
+  fail_unless (position == 10 * GST_SECOND, "position is wrong");
+
+  /* Since we are paused and the preroll queue has a length of 1, this function
+   * will return immediatly. The EOS will complete the preroll and the  
+   * position should now be 10 seconds. */
+  GST_DEBUG ("pushing EOS");
+  event = gst_event_new_eos ();
+  res = gst_pad_send_event (sinkpad, event);
+  fail_unless (res == TRUE, "no TRUE return");
+
+  /* check if position is still 10 seconds */
+  format = GST_FORMAT_TIME;
+  gst_element_query_position (sink, &format, &position);
+  GST_DEBUG ("EOS position %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
+  fail_unless (position == 10 * GST_SECOND, "position is wrong");
+
+  gst_object_unref (sinkpad);
+
+  gst_element_set_state (sink, GST_STATE_NULL);
+  gst_object_unref (sink);
+  gst_object_unref (bus);
+}
+
+GST_END_TEST;
+
+static GMutex *preroll_lock;
+static GCond *preroll_cond;
+
+static void
+test_async_false_seek_preroll (GstElement * elem, GstBuffer * buf,
+    GstPad * pad, gpointer data)
+{
+  g_mutex_lock (preroll_lock);
+  GST_DEBUG ("Got preroll buffer %p", buf);
+  g_cond_signal (preroll_cond);
+  g_mutex_unlock (preroll_lock);
+}
+
+static void
+test_async_false_seek_handoff (GstElement * elem, GstBuffer * buf,
+    GstPad * pad, gpointer data)
+{
+  /* should never be reached, we never go to PLAYING */
+  GST_DEBUG ("Got handoff buffer %p", buf);
+  fail_unless (FALSE);
+}
+
+GST_START_TEST (test_async_false_seek)
+{
+  GstElement *pipeline, *source, *sink;
+
+  preroll_lock = g_mutex_new ();
+  preroll_cond = g_cond_new ();
+
+  /* Create gstreamer elements */
+  pipeline = gst_pipeline_new ("test-pipeline");
+  source = gst_element_factory_make ("fakesrc", "file-source");
+  sink = gst_element_factory_make ("fakesink", "audio-output");
+
+  g_object_set (G_OBJECT (sink), "async", FALSE, NULL);
+  g_object_set (G_OBJECT (sink), "num-buffers", 10, NULL);
+  g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
+
+  g_signal_connect (sink, "handoff", G_CALLBACK (test_async_false_seek_handoff),
+      NULL);
+  g_signal_connect (sink, "preroll-handoff",
+      G_CALLBACK (test_async_false_seek_preroll), NULL);
+
+  /* we add all elements into the pipeline */
+  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
+
+  /* we link the elements together */
+  gst_element_link (source, sink);
+
+  GST_DEBUG ("Now pausing");
+  g_mutex_lock (preroll_lock);
+  gst_element_set_state (pipeline, GST_STATE_PAUSED);
+
+  /* wait for preroll */
+  GST_DEBUG ("wait for preroll");
+  g_cond_wait (preroll_cond, preroll_lock);
+  g_mutex_unlock (preroll_lock);
+
+  g_mutex_lock (preroll_lock);
+  GST_DEBUG ("Seeking");
+  fail_unless (gst_element_seek (pipeline, 1.0, GST_FORMAT_BYTES,
+          GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1));
+
+  GST_DEBUG ("wait for new preroll");
+  /* this either prerolls or fails */
+  g_cond_wait (preroll_cond, preroll_lock);
+  g_mutex_unlock (preroll_lock);
+
+  GST_DEBUG ("bring pipe to state NULL");
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+
+  GST_DEBUG ("Deleting pipeline");
+  gst_object_unref (GST_OBJECT (pipeline));
+
+  g_mutex_free (preroll_lock);
+  g_cond_free (preroll_cond);
+}
+
+GST_END_TEST;
+
+static GMutex *handoff_lock;
+static GCond *handoff_cond;
+
+static void
+test_async_false_seek_in_playing_handoff (GstElement * elem, GstBuffer * buf,
+    GstPad * pad, gpointer data)
+{
+  g_mutex_lock (handoff_lock);
+  GST_DEBUG ("Got handoff buffer %p", buf);
+  g_cond_signal (handoff_cond);
+  g_mutex_unlock (handoff_lock);
+}
+
+GST_START_TEST (test_async_false_seek_in_playing)
+{
+  GstElement *pipeline, *source, *sink;
+
+  handoff_lock = g_mutex_new ();
+  handoff_cond = g_cond_new ();
+
+  /* Create gstreamer elements */
+  pipeline = gst_pipeline_new ("test-pipeline");
+  source = gst_element_factory_make ("fakesrc", "fake-source");
+  sink = gst_element_factory_make ("fakesink", "fake-output");
+
+  g_object_set (G_OBJECT (sink), "async", FALSE, NULL);
+  g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
+
+  g_signal_connect (sink, "handoff",
+      G_CALLBACK (test_async_false_seek_in_playing_handoff), NULL);
+
+  /* we add all elements into the pipeline */
+  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
+
+  /* we link the elements together */
+  gst_element_link (source, sink);
+
+  GST_DEBUG ("Now playing");
+  gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+  g_mutex_lock (handoff_lock);
+  GST_DEBUG ("wait for handoff buffer");
+  g_cond_wait (handoff_cond, handoff_lock);
+  g_mutex_unlock (handoff_lock);
+
+  GST_DEBUG ("Seeking");
+  fail_unless (gst_element_seek (source, 1.0, GST_FORMAT_BYTES,
+          GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, -1));
+
+  g_mutex_lock (handoff_lock);
+  GST_DEBUG ("wait for handoff buffer");
+  g_cond_wait (handoff_cond, handoff_lock);
+  g_mutex_unlock (handoff_lock);
+
+  GST_DEBUG ("bring pipe to state NULL");
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+
+  GST_DEBUG ("Deleting pipeline");
+  gst_object_unref (GST_OBJECT (pipeline));
+
+  g_mutex_free (handoff_lock);
+  g_cond_free (handoff_cond);
+}
+
+GST_END_TEST;
+
 /* test: try changing state of sinks */
-Suite *
+static Suite *
 gst_sinks_suite (void)
 {
   Suite *s = suite_create ("Sinks");
   TCase *tc_chain = tcase_create ("general");
+  guint timeout = 10;
+
+  /* time out after 10s, not the default 3, we need this for the last test.
+   * We need a longer timeout when running under valgrind though. */
+  if (g_getenv ("CK_DEFAULT_TIMEOUT") != NULL)
+    timeout = MAX (10, atoi (g_getenv ("CK_DEFAULT_TIMEOUT")));
+  tcase_set_timeout (tc_chain, timeout);
 
   suite_add_tcase (s, tc_chain);
   tcase_add_test (tc_chain, test_sink);
@@ -465,6 +1416,19 @@ gst_sinks_suite (void)
   tcase_add_test (tc_chain, test_livesrc_sink);
   tcase_add_test (tc_chain, test_livesrc2_sink);
   tcase_add_test (tc_chain, test_livesrc3_sink);
+  tcase_add_test (tc_chain, test_locked_sink);
+  tcase_add_test (tc_chain, test_unlinked_live);
+  tcase_add_test (tc_chain, test_delayed_async);
+  tcase_add_test (tc_chain, test_added_async);
+  tcase_add_test (tc_chain, test_added_async2);
+  tcase_add_test (tc_chain, test_add_live);
+  tcase_add_test (tc_chain, test_add_live2);
+  tcase_add_test (tc_chain, test_bin_live);
+  tcase_add_test (tc_chain, test_fake_eos);
+  tcase_add_test (tc_chain, test_async_done);
+  tcase_add_test (tc_chain, test_async_done_eos);
+  tcase_add_test (tc_chain, test_async_false_seek);
+  tcase_add_test (tc_chain, test_async_false_seek_in_playing);
 
   return s;
 }