check: Add GstHarness convenience API for unit tests
authorHavard Graff <havard.graff@gmail.com>
Mon, 16 Dec 2013 09:47:47 +0000 (10:47 +0100)
committerTim-Philipp Müller <tim@centricular.com>
Mon, 6 Jul 2015 22:36:37 +0000 (23:36 +0100)
http://gstconf.ubicast.tv/videos/gstharness-again-a-follow-up/

https://bugzilla.gnome.org/show_bug.cgi?id=751916

libs/gst/check/Makefile.am
libs/gst/check/gstharness.c [new file with mode: 0644]
libs/gst/check/gstharness.h [new file with mode: 0644]

index d80e3fd..a08c459 100644 (file)
@@ -9,6 +9,7 @@ libgstcheck_@GST_API_VERSION@_la_SOURCES =      \
        gstbufferstraw.c                        \
        gstcheck.c                              \
        gstconsistencychecker.c                 \
+       gstharness.c                            \
        gsttestclock.c
 
 libgstcheck_@GST_API_VERSION@_la_CFLAGS = $(GST_OBJ_CFLAGS) \
@@ -29,6 +30,7 @@ libgstcheck_@GST_API_VERSION@include_HEADERS =        \
        gstbufferstraw.h                        \
        gstcheck.h                              \
        gstconsistencychecker.h                 \
+       gstharness.h                            \
        gsttestclock.h
 
 nodist_libgstcheck_@GST_API_VERSION@include_HEADERS =  \
@@ -93,6 +95,75 @@ LIBGSTCHECK_EXPORTED_FUNCS = \
        gst_consistency_checker_new \
        gst_consistency_checker_reset \
        gst_consistency_checker_free \
+       gst_harness_add_element_src_pad \
+       gst_harness_add_probe \
+       gst_harness_add_sink \
+       gst_harness_add_sink_parse \
+       gst_harness_add_src \
+       gst_harness_add_src_parse \
+       gst_harness_buffers_received \
+       gst_harness_buffers_in_queue \
+       gst_harness_crank_multiple_clock_waits \
+       gst_harness_crank_single_clock_wait \
+       gst_harness_create_buffer \
+       gst_harness_dump_to_file \
+       gst_harness_events_received \
+       gst_harness_events_in_queue \
+       gst_harness_find_element \
+       gst_harness_get \
+       gst_harness_get_allocator \
+       gst_harness_get_last_pushed_timestamp \
+       gst_harness_get_testclock \
+       gst_harness_new \
+       gst_harness_new_full \
+       gst_harness_new_parse \
+       gst_harness_new_with_element \
+       gst_harness_new_with_padnames \
+       gst_harness_new_with_templates \
+       gst_harness_play \
+       gst_harness_pull \
+       gst_harness_pull_event \
+       gst_harness_pull_upstream_event \
+       gst_harness_push \
+       gst_harness_push_and_pull \
+       gst_harness_push_event \
+       gst_harness_push_from_src \
+       gst_harness_push_to_sink \
+       gst_harness_query_latency \
+       gst_harness_push_upstream_event \
+       gst_harness_set \
+       gst_harness_set_caps \
+       gst_harness_set_caps_str \
+       gst_harness_set_drop_buffers \
+       gst_harness_set_blocking_push_mode \
+       gst_harness_set_propose_allocator \
+       gst_harness_set_sink_caps \
+       gst_harness_set_src_caps \
+       gst_harness_set_src_caps_str \
+       gst_harness_set_sink_caps_str \
+       gst_harness_set_time \
+       gst_harness_set_upstream_latency \
+       gst_harness_sink_push_many \
+       gst_harness_src_crank_and_push_many \
+       gst_harness_src_push_event \
+       gst_harness_stress_custom_start \
+       gst_harness_stress_property_start_full \
+       gst_harness_stress_push_buffer_start_full \
+       gst_harness_stress_push_buffer_with_cb_start_full \
+       gst_harness_stress_push_event_start_full \
+       gst_harness_stress_push_upstream_event_start_full \
+       gst_harness_stress_requestpad_start_full \
+       gst_harness_stress_statechange_start_full \
+       gst_harness_stress_thread_stop \
+       gst_harness_teardown \
+       gst_harness_try_pull \
+       gst_harness_try_pull_event \
+       gst_harness_try_pull_upstream_event \
+       gst_harness_upstream_events_received \
+       gst_harness_upstream_events_in_queue \
+       gst_harness_use_systemclock \
+       gst_harness_use_testclock \
+       gst_harness_wait_for_clock_id_waits \
        gst_test_clock_get_type \
        gst_test_clock_new \
        gst_test_clock_new_with_start_time \
diff --git a/libs/gst/check/gstharness.c b/libs/gst/check/gstharness.c
new file mode 100644 (file)
index 0000000..2f7ce28
--- /dev/null
@@ -0,0 +1,2885 @@
+/* GstHarness - A test-harness for GStreamer testing
+ *
+ * Copyright (C) 2012-2015 Pexip <pexip.com>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:gstharness
+ * @short_description: A test-harness for writing GStreamer unit tests
+ * @see_also: #GstTestClock,\
+ *
+ * #GstHarness is ment to make writing unit test for GStreamer much easier.
+ * It can be though of as a way of treating a #GstElement as a black box,
+ * deterministially feeding it data, and controlling what data it outputs.
+ *
+ * The basic structure of #GstHarness is two "floating" #GstPads, that connects
+ * to the harnessed #GstElement src and sink #GstPads like so:
+ *
+ *           __________________________
+ *  _____   |  _____            _____  |   _____
+ * |     |  | |     |          |     | |  |     |
+ * | src |--+-| sink|  Element | src |-+--| sink|
+ * |_____|  | |_____|          |_____| |  |_____|
+ *          |__________________________|
+ *
+ *
+ * With this, you can now simulate any environment the #GstElement might find
+ * itself in. By specifying the #GstCaps of the harness #GstPads, using
+ * functions like gst_harness_set_src_caps or gst_harness_set_sink_caps_str,
+ * you can test how the #GstElement interacts with different capssets.
+ *
+ * Your harnessed #GstElement can of course also be a bin, and using
+ * gst_harness_new_parse supporting standard gst-launch syntax, you can
+ * easily test a whole pipeline instead of just one element.
+ *
+ * You can then go on to push #GstBuffers and #GstEvents on to the srcpad,
+ * using functions like gst_harness_push and gst_harness_push_event, and
+ * then pull them out to examine them with gst_harness_pull and
+ * gst_harness_pull_event.
+ *
+ * <example>
+ * <title>A simple buffer-in buffer-out example</title>
+ *   <programlisting language="c">
+ *   #include &lt;gst/gst.h&gt;
+ *   #include &lt;gst/check/gstharness.h&gt;
+ *   GstHarness *h;
+ *   GstBuffer *in_buf;
+ *   GstBuffer *out_buf;
+ *
+ *   // attach the harness to the src and sink pad of GstQueue
+ *   h = gst_harness_new ("queue");
+ *
+ *   // we must specify a caps before pushing buffers
+ *   gst_harness_set_src_caps_str (h, "mycaps");
+ *
+ *   // create a buffer of size 42
+ *   in_buf = gst_harness_create_buffer (h, 42);
+ *
+ *   // push the buffer into the queue
+ *   gst_harness_push (h, in_buf);
+ *
+ *   // pull the buffer from the queue
+ *   out_buf = gst_harness_pull (h);
+ *
+ *   // validate the buffer in is the same as buffer out
+ *   fail_unless (in_buf == out_buf);
+ *
+ *   // cleanup
+ *   gst_buffer_unref (out_buf);
+ *   gst_harness_teardown (h);
+ *
+ *   </programlisting>
+ * </example>
+ *
+ * Another main feature of the #GstHarness is its integration with the
+ * #GstTestClock. Operating the #GstTestClock can be very challenging, but
+ * #GstHarness simplifies some of the most desired actions a lot, like wanting
+ * to manually advance the clock while at the same time releasing a #GstClockID
+ * that is waiting, with functions like gst_harness_crank_single_clock_wait.
+ *
+ * #GstHarness also supports sub-harnesses, as a way of generating and
+ * validating data. A sub-harness is another #GstHarness that is managed by
+ * the "parent" harness, and can either be created by using the standard
+ * gst_harness_new type functions directly on the (GstHarness *)->src_harness,
+ * or using the much more convenient gst_harness_add_src or
+ * gst_harness_add_sink_parse. If you have a decoder-element you want to test,
+ * (like vp8dec) it can be very useful to add a src-harness with both a
+ * src-element (videotestsrc) and an encoder (vp8enc) to feed the decoder data
+ * with different configurations, by simply doing:
+ *
+ * <example>
+ * <programlisting language="c">
+ *   GstHarness * h = gst_harness_new (h, "vp8dec");
+ *   gst_harness_add_src_parse (h, "videotestsrc is-live=1 ! vp8enc", TRUE);
+ * </programlisting>
+ * </example>
+ *
+ * and then feeding it data with:
+ *
+ * <example>
+ * <programlisting language="c">
+ * gst_harness_push_from_src (h);
+ * </programlisting>
+ * </example>
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstharness.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+static void gst_harness_stress_free (GstHarnessThread * t);
+
+#define HARNESS_KEY "harness"
+#define HARNESS_REF "harness-ref"
+
+static GstStaticPadTemplate hsrctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+static GstStaticPadTemplate hsinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+struct _GstHarnessPrivate {
+  gchar * element_sinkpad_name;
+  gchar * element_srcpad_name;
+
+  GstCaps * src_caps;
+  GstCaps * sink_caps;
+  GstPad * sink_forward_pad;
+
+  volatile gint recv_buffers;
+  volatile gint recv_events;
+  volatile gint recv_upstream_events;
+
+  GAsyncQueue * buffer_queue;
+  GAsyncQueue * src_event_queue;
+  GAsyncQueue * sink_event_queue;
+
+  GstClockTime latency_min;
+  GstClockTime latency_max;
+  gboolean has_clock_wait;
+  gboolean drop_buffers;
+  GstClockTime last_push_ts;
+
+  GstBufferPool * pool;
+  GstAllocator * allocator;
+  GstAllocationParams allocation_params;
+  GstAllocator * propose_allocator;
+  GstAllocationParams propose_allocation_params;
+
+  gboolean blocking_push_mode;
+  GCond blocking_push_cond;
+  GMutex blocking_push_mutex;
+
+  GPtrArray * stress;
+};
+
+static GstFlowReturn
+gst_harness_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
+{
+  GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY);
+  GstHarnessPrivate *priv = h->priv;
+  (void) parent;
+  g_assert (h != NULL);
+  g_mutex_lock (&priv->blocking_push_mutex);
+  g_atomic_int_inc (&priv->recv_buffers);
+
+  if (priv->drop_buffers)
+    gst_buffer_unref (buffer);
+  else
+    g_async_queue_push (priv->buffer_queue, buffer);
+
+  if (priv->blocking_push_mode) {
+    g_cond_wait (&priv->blocking_push_cond, &priv->blocking_push_mutex);
+  }
+  g_mutex_unlock (&priv->blocking_push_mutex);
+
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_harness_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY);
+  GstHarnessPrivate *priv = h->priv;
+  (void) parent;
+  g_assert (h != NULL);
+  g_atomic_int_inc (&priv->recv_upstream_events);
+  g_async_queue_push (priv->src_event_queue, event);
+  return TRUE;
+}
+
+static gboolean
+gst_harness_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY);
+  GstHarnessPrivate *priv = h->priv;
+  gboolean forward;
+
+  g_assert (h != NULL);
+  (void) parent;
+  g_atomic_int_inc (&priv->recv_events);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_STREAM_START:
+    case GST_EVENT_CAPS:
+    case GST_EVENT_SEGMENT:
+      forward = TRUE;
+      break;
+    default:
+      forward = FALSE;
+      break;
+  }
+
+  if (forward && priv->sink_forward_pad) {
+    gst_pad_push_event (priv->sink_forward_pad, event);
+  } else {
+    g_async_queue_push (priv->sink_event_queue, event);
+  }
+
+  return TRUE;
+}
+
+static void
+gst_harness_decide_allocation (GstHarness * h, GstCaps * caps)
+{
+  GstHarnessPrivate *priv = h->priv;
+  GstQuery *query;
+  GstAllocator *allocator;
+  GstAllocationParams params;
+  GstBufferPool *pool = NULL;
+  guint size, min, max;
+
+  query = gst_query_new_allocation (caps, FALSE);
+  gst_pad_peer_query (h->srcpad, query);
+
+  if (gst_query_get_n_allocation_params (query) > 0) {
+    gst_query_parse_nth_allocation_param (query, 0, &allocator, &params);
+  } else {
+    allocator = NULL;
+    gst_allocation_params_init (&params);
+  }
+
+  if (gst_query_get_n_allocation_pools (query) > 0) {
+    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+#if 0
+    /* Most elements create their own pools if pool == NULL. Not sure if we
+     * want to do that in the harness since we may want to test the pool
+     * implementation of the elements. Not creating a pool will however ignore
+     * the returned size. */
+    if (pool == NULL)
+      pool = gst_buffer_pool_new ();
+#endif
+  } else {
+    pool = NULL;
+    size = min = max = 0;
+  }
+  gst_query_unref (query);
+
+  if (pool) {
+    GstStructure *config = gst_buffer_pool_get_config (pool);
+    gst_buffer_pool_config_set_params (config, caps, size, min, max);
+    gst_buffer_pool_config_set_allocator (config, allocator, &params);
+    gst_buffer_pool_set_config (pool, config);
+  }
+
+  if (pool != priv->pool) {
+    if (priv->pool != NULL)
+      gst_buffer_pool_set_active (priv->pool, FALSE);
+    if (pool)
+      gst_buffer_pool_set_active (pool, TRUE);
+  }
+
+  priv->allocation_params = params;
+  if (priv->allocator)
+    gst_object_unref (priv->allocator);
+  priv->allocator = allocator;
+  if (priv->pool)
+    gst_object_unref (priv->pool);
+  priv->pool = pool;
+}
+
+static void
+gst_harness_negotiate (GstHarness * h)
+{
+  GstCaps *caps;
+
+  caps = gst_pad_get_current_caps (h->srcpad);
+  if (caps != NULL) {
+    gst_harness_decide_allocation (h, caps);
+    gst_caps_unref (caps);
+  } else {
+    GST_FIXME_OBJECT (h, "Cannot negotiate allocation because caps is not set");
+  }
+}
+
+static gboolean
+gst_harness_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+  GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY);
+  GstHarnessPrivate *priv = h->priv;
+  gboolean res = TRUE;
+  g_assert (h != NULL);
+
+  // FIXME: forward all queries?
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_LATENCY:
+      gst_query_set_latency (query, TRUE, priv->latency_min, priv->latency_max);
+      break;
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *caps, *filter = NULL;
+
+      caps = priv->sink_caps ? gst_caps_ref (priv->sink_caps) : gst_caps_new_any ();
+
+      gst_query_parse_caps (query, &filter);
+      if (filter != NULL) {
+        gst_caps_take (&caps,
+            gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST));
+      }
+
+      gst_query_set_caps_result (query, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    case GST_QUERY_ALLOCATION:
+    {
+      if (priv->sink_forward_pad != NULL) {
+        GstPad *peer = gst_pad_get_peer (priv->sink_forward_pad);
+        g_assert (peer != NULL);
+        res = gst_pad_query (peer, query);
+        gst_object_unref (peer);
+      } else {
+        GstCaps *caps;
+        gboolean need_pool;
+
+        gst_query_parse_allocation (query, &caps, &need_pool);
+
+        /* FIXME: Can this be removed? */
+        g_assert_cmpuint (0, ==, gst_query_get_n_allocation_params (query));
+        gst_query_add_allocation_param (query,
+            priv->propose_allocator, &priv->propose_allocation_params);
+
+        GST_DEBUG_OBJECT (pad, "proposing allocation %" GST_PTR_FORMAT,
+            priv->propose_allocator);
+      }
+      break;
+    }
+    default:
+      res = gst_pad_query_default (pad, parent, query);
+  }
+
+  return res;
+}
+
+static gboolean
+gst_harness_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+  GstHarness *h = g_object_get_data (G_OBJECT (pad), HARNESS_KEY);
+  GstHarnessPrivate *priv = h->priv;
+  gboolean res = TRUE;
+  g_assert (h != NULL);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_LATENCY:
+      gst_query_set_latency (query, TRUE, priv->latency_min, priv->latency_max);
+      break;
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *caps, *filter = NULL;
+
+      caps = priv->src_caps ? gst_caps_ref (priv->src_caps) : gst_caps_new_any ();
+
+      gst_query_parse_caps (query, &filter);
+      if (filter != NULL) {
+        gst_caps_take (&caps,
+            gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST));
+      }
+
+      gst_query_set_caps_result (query, caps);
+      gst_caps_unref (caps);
+    }
+      break;
+    default:
+      res = gst_pad_query_default (pad, parent, query);
+  }
+  return res;
+}
+
+static void
+gst_harness_element_ref (GstHarness * h)
+{
+  guint *data = g_object_get_data (G_OBJECT (h->element), HARNESS_REF);
+  if (data == NULL) {
+    data = g_new0 (guint, 1);
+    *data = 1;
+    g_object_set_data_full (G_OBJECT (h->element), HARNESS_REF, data, g_free);
+  } else {
+    (*data)++;
+  }
+}
+
+static guint
+gst_harness_element_unref (GstHarness * h)
+{
+  guint *data = g_object_get_data (G_OBJECT (h->element), HARNESS_REF);
+  g_assert (data != NULL);
+  (*data)--;
+  return *data;
+}
+
+static void
+gst_harness_link_element_srcpad (GstHarness * h,
+    const gchar * element_srcpad_name)
+{
+  GstHarnessPrivate *priv = h->priv;
+  GstPad *srcpad = gst_element_get_static_pad (h->element,
+      element_srcpad_name);
+  if (srcpad == NULL)
+    srcpad = gst_element_get_request_pad (h->element, element_srcpad_name);
+  g_assert (srcpad);
+  g_assert_cmpint (gst_pad_link (srcpad, h->sinkpad), ==, GST_PAD_LINK_OK);
+  g_free (priv->element_srcpad_name);
+  priv->element_srcpad_name = gst_pad_get_name (srcpad);
+
+  gst_object_unref (srcpad);
+}
+
+static void
+gst_harness_link_element_sinkpad (GstHarness * h,
+    const gchar * element_sinkpad_name)
+{
+  GstHarnessPrivate *priv = h->priv;
+  GstPad *sinkpad = gst_element_get_static_pad (h->element,
+      element_sinkpad_name);
+  if (sinkpad == NULL)
+    sinkpad = gst_element_get_request_pad (h->element, element_sinkpad_name);
+  g_assert (sinkpad);
+  g_assert_cmpint (gst_pad_link (h->srcpad, sinkpad), ==, GST_PAD_LINK_OK);
+  g_free (priv->element_sinkpad_name);
+  priv->element_sinkpad_name = gst_pad_get_name (sinkpad);
+
+  gst_object_unref (sinkpad);
+}
+
+static void
+gst_harness_setup_src_pad (GstHarness * h,
+    GstStaticPadTemplate * src_tmpl, const gchar * element_sinkpad_name)
+{
+  GstHarnessPrivate *priv = h->priv;
+  g_assert (src_tmpl);
+
+  priv->src_event_queue =
+      g_async_queue_new_full ((GDestroyNotify) gst_event_unref);
+
+  /* sending pad */
+  h->srcpad = gst_pad_new_from_static_template (src_tmpl, "src");
+  g_assert (h->srcpad);
+  g_object_set_data (G_OBJECT (h->srcpad), HARNESS_KEY, h);
+
+  gst_pad_set_query_function (h->srcpad, gst_harness_src_query);
+  gst_pad_set_event_function (h->srcpad, gst_harness_src_event);
+
+  gst_pad_set_active (h->srcpad, TRUE);
+
+  if (element_sinkpad_name)
+    gst_harness_link_element_sinkpad (h, element_sinkpad_name);
+}
+
+static void
+gst_harness_setup_sink_pad (GstHarness * h,
+    GstStaticPadTemplate * sink_tmpl, const gchar * element_srcpad_name)
+{
+  GstHarnessPrivate *priv = h->priv;
+  g_assert (sink_tmpl);
+
+  priv->buffer_queue = g_async_queue_new_full (
+      (GDestroyNotify) gst_buffer_unref);
+  priv->sink_event_queue = g_async_queue_new_full (
+      (GDestroyNotify) gst_event_unref);
+
+  /* receiving pad */
+  h->sinkpad = gst_pad_new_from_static_template (sink_tmpl, "sink");
+  g_assert (h->sinkpad);
+  g_object_set_data (G_OBJECT (h->sinkpad), HARNESS_KEY, h);
+
+  gst_pad_set_chain_function (h->sinkpad, gst_harness_chain);
+  gst_pad_set_query_function (h->sinkpad, gst_harness_sink_query);
+  gst_pad_set_event_function (h->sinkpad, gst_harness_sink_event);
+
+  gst_pad_set_active (h->sinkpad, TRUE);
+
+  if (element_srcpad_name)
+    gst_harness_link_element_srcpad (h, element_srcpad_name);
+}
+
+static void
+turn_async_and_sync_off (GstElement * element)
+{
+  GObjectClass *class = G_OBJECT_GET_CLASS (element);
+  if (g_object_class_find_property (class, "async"))
+    g_object_set (element, "async", FALSE, NULL);
+  if (g_object_class_find_property (class, "sync"))
+    g_object_set (element, "sync", FALSE, NULL);
+}
+
+static gboolean
+gst_pad_is_request_pad (GstPad * pad)
+{
+  GstPadTemplate *temp;
+  if (pad == NULL)
+    return FALSE;
+  temp = gst_pad_get_pad_template (pad);
+  if (temp == NULL)
+    return FALSE;
+  return GST_PAD_TEMPLATE_PRESENCE (temp) == GST_PAD_REQUEST;
+}
+
+/**
+ * gst_harness_new_full: (skip)
+ * @element: a #GstElement to attach the harness to (transfer none)
+ * @hsrc: (allow-none): a #GstStaticPadTemplate describing the harness srcpad.
+ * %NULL will not create a harness srcpad.
+ * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that is
+ * then linked to the harness srcpad. Can be a static or request or a sometimes
+ * pad that has been added. %NULL will not get/request a sinkpad from the
+ * element. (Like if the element is a src)
+ * @hsink: (allow-none): a #GstStaticPadTemplate describing the harness sinkpad.
+ * %NULL will not create a harness sinkpad.
+ * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is
+ * then linked to the harness sinkpad, similar to the @sinkpad.
+ *
+ * Creates a new harness.
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new_full (GstElement * element,
+    GstStaticPadTemplate * hsrc, const gchar * sinkpad,
+    GstStaticPadTemplate * hsink, const gchar * srcpad)
+{
+  GstHarness *h;
+  GstHarnessPrivate *priv;
+  gboolean is_sink, is_src;
+
+  g_return_val_if_fail (element != NULL, NULL);
+
+  h = g_new0 (GstHarness, 1);
+  g_assert (h != NULL);
+  h->priv = g_new0 (GstHarnessPrivate, 1);
+  priv = h->priv;
+
+  GST_DEBUG_OBJECT (h, "about to create new harness %p", h);
+  h->element = gst_object_ref (element);
+  priv->last_push_ts = GST_CLOCK_TIME_NONE;
+  priv->latency_min = 0;
+  priv->latency_max = GST_CLOCK_TIME_NONE;
+  priv->drop_buffers = FALSE;
+
+  priv->propose_allocator = NULL;
+  gst_allocation_params_init (&priv->propose_allocation_params);
+
+  g_mutex_init (&priv->blocking_push_mutex);
+  g_cond_init (&priv->blocking_push_cond);
+
+  is_src = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SOURCE);
+  is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK);
+
+  /* setup the loose srcpad linked to the element sinkpad */
+  if (!is_src && hsrc)
+    gst_harness_setup_src_pad (h, hsrc, sinkpad);
+
+  /* setup the loose sinkpad linked to the element srcpad */
+  if (!is_sink && hsink)
+    gst_harness_setup_sink_pad (h, hsink, srcpad);
+
+  /* as a harness sink, we should not need sync and async */
+  if (is_sink)
+    turn_async_and_sync_off (h->element);
+
+  if (h->srcpad != NULL) {
+    gchar *stream_id = g_strdup_printf ("%s-%p",
+        GST_OBJECT_NAME (h->element), h);
+    g_assert (gst_pad_push_event (h->srcpad,
+            gst_event_new_stream_start (stream_id)));
+    g_free (stream_id);
+  }
+
+  /* don't start sources, they start producing data! */
+  if (!is_src)
+    gst_harness_play (h);
+
+  gst_harness_element_ref (h);
+
+  GST_DEBUG_OBJECT (h, "created new harness %p "
+      "with srcpad (%p, %s, %s) and sinkpad (%p, %s, %s)",
+      h, h->srcpad, GST_DEBUG_PAD_NAME (h->srcpad),
+      h->sinkpad, GST_DEBUG_PAD_NAME (h->sinkpad));
+
+  priv->stress = g_ptr_array_new_with_free_func (
+      (GDestroyNotify) gst_harness_stress_free);
+
+  return h;
+}
+
+/**
+ * gst_harness_new_with_element: (skip)
+ * @element: a #GstElement to attach the harness to (transfer none)
+ * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that
+ * is then linked to the harness srcpad. %NULL does not attach a sinkpad
+ * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is
+ * then linked to the harness sinkpad. %NULL does not attach a srcpad
+ *
+ * Creates a new harness. Works in the same way as gst_harness_new_full, only
+ * that generic padtemplates are used for the harness src and sinkpads, which
+ * will be sufficient in most usecases.
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new_with_element (GstElement * element,
+    const gchar * sinkpad, const gchar * srcpad)
+{
+  return gst_harness_new_full (element,
+      &hsrctemplate, sinkpad, &hsinktemplate, srcpad);
+}
+
+/**
+ * gst_harness_new_with_padnames: (skip)
+ * @element_name: a #gchar describing the #GstElement name
+ * @sinkpad: (allow-none): a #gchar with the name of the element sinkpad that
+ * is then linked to the harness srcpad. %NULL does not attach a sinkpad
+ * @srcpad: (allow-none): a #gchar with the name of the element srcpad that is
+ * then linked to the harness sinkpad. %NULL does not attach a srcpad
+ *
+ * Creates a new harness. Works in the same way as gst_harness_new_with_element,
+ * except you specify the factoryname of the #GstElement
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new_with_padnames (const gchar * element_name,
+    const gchar * sinkpad, const gchar * srcpad)
+{
+  GstHarness *h;
+  GstElement *element = gst_element_factory_make (element_name, NULL);
+  g_assert (element != NULL);
+
+  h = gst_harness_new_with_element (element, sinkpad, srcpad);
+  gst_object_unref (element);
+  return h;
+}
+
+/**
+ * gst_harness_new_with_templates: (skip)
+ * @element_name: a #gchar describing the #GstElement name
+ * @hsrc: (allow-none): a #GstStaticPadTemplate describing the harness srcpad.
+ * %NULL will not create a harness srcpad.
+ * @hsink: (allow-none): a #GstStaticPadTemplate describing the harness sinkpad.
+ * %NULL will not create a harness sinkpad.
+ *
+ * Creates a new harness, like gst_harness_new_full, except it
+ * assumes the #GstElement sinkpad is named "sink" and srcpad is named "src"
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new_with_templates (const gchar * element_name,
+    GstStaticPadTemplate * hsrc, GstStaticPadTemplate * hsink)
+{
+  GstHarness *h;
+  GstElement *element = gst_element_factory_make (element_name, NULL);
+  g_assert (element != NULL);
+
+  h = gst_harness_new_full (element, hsrc, "sink", hsink, "src");
+  gst_object_unref (element);
+  return h;
+}
+
+/**
+ * gst_harness_new: (skip)
+ * @element_name: a #gchar describing the #GstElement name
+ *
+ * Creates a new harness. Works like gst_harness_new_with_padnames, except it
+ * assumes the #GstElement sinkpad is named "sink" and srcpad is named "src"
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new (const gchar * element_name)
+{
+  return gst_harness_new_with_padnames (element_name, "sink", "src");
+}
+
+/**
+ * gst_harness_new_parse: (skip)
+ * @launchline: a #gchar describing a gst-launch type line
+ *
+ * Creates a new harness, parsing the @launchline and putting that in a #GstBin,
+ * and then attches the harness to the bin.
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstHarness, or %NULL if the harness could
+ * not be created
+ *
+ * Since: 1.6
+ */
+GstHarness *
+gst_harness_new_parse (const gchar * launchline)
+{
+  GstHarness *h;
+  GstBin *bin;
+  gchar *desc;
+  GstPad *pad;
+  GstIterator *iter;
+  gboolean done = FALSE;
+
+  g_return_val_if_fail (launchline != NULL, NULL);
+
+  desc = g_strdup_printf ("bin.( %s )", launchline);
+  bin =
+      (GstBin *) gst_parse_launch_full (desc, NULL, GST_PARSE_FLAG_NONE, NULL);
+  g_free (desc);
+
+  if (G_UNLIKELY (bin == NULL))
+    return NULL;
+
+  /* find pads and ghost them if necessary */
+  if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SRC)) != NULL) {
+    gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("src", pad));
+    gst_object_unref (pad);
+  }
+  if ((pad = gst_bin_find_unlinked_pad (bin, GST_PAD_SINK)) != NULL) {
+    gst_element_add_pad (GST_ELEMENT (bin), gst_ghost_pad_new ("sink", pad));
+    gst_object_unref (pad);
+  }
+
+  iter = gst_bin_iterate_sinks (bin);
+  while (!done) {
+    GValue item = { 0, };
+
+    switch (gst_iterator_next (iter, &item)) {
+      case GST_ITERATOR_OK:
+        turn_async_and_sync_off (GST_ELEMENT (g_value_get_object (&item)));
+        g_value_reset (&item);
+        break;
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (iter);
+        break;
+      case GST_ITERATOR_ERROR:
+        gst_object_unref (bin);
+        gst_iterator_free (iter);
+        g_return_val_if_reached (NULL);
+        break;
+    }
+  }
+  gst_iterator_free (iter);
+
+  h = gst_harness_new_full (GST_ELEMENT_CAST (bin),
+      &hsrctemplate, "sink", &hsinktemplate, "src");
+  gst_object_unref (bin);
+  return h;
+}
+
+/**
+ * gst_harness_teardown:
+ * @h: a #GstHarness
+ *
+ * Tears down a @GstHarness, freeing all resources allocated using it.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_teardown (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  if (priv->blocking_push_mode) {
+    g_mutex_lock (&priv->blocking_push_mutex);
+    priv->blocking_push_mode = FALSE;
+    g_cond_signal (&priv->blocking_push_cond);
+    g_mutex_unlock (&priv->blocking_push_mutex);
+  }
+
+  if (h->src_harness) {
+    gst_harness_teardown (h->src_harness);
+  }
+
+  if (h->sink_harness) {
+    gst_harness_teardown (h->sink_harness);
+  }
+
+  if (priv->src_caps)
+    gst_caps_unref (priv->src_caps);
+
+  if (priv->sink_caps)
+    gst_caps_unref (priv->sink_caps);
+
+  if (h->srcpad) {
+    if (gst_pad_is_request_pad (GST_PAD_PEER (h->srcpad)))
+      gst_element_release_request_pad (h->element, GST_PAD_PEER (h->srcpad));
+    g_free (priv->element_sinkpad_name);
+
+    gst_pad_set_active (h->srcpad, FALSE);
+    gst_object_unref (h->srcpad);
+
+    g_async_queue_unref (priv->src_event_queue);
+  }
+
+  if (h->sinkpad) {
+    if (gst_pad_is_request_pad (GST_PAD_PEER (h->sinkpad)))
+      gst_element_release_request_pad (h->element, GST_PAD_PEER (h->sinkpad));
+    g_free (priv->element_srcpad_name);
+
+    gst_pad_set_active (h->sinkpad, FALSE);
+    gst_object_unref (h->sinkpad);
+
+    g_async_queue_unref (priv->buffer_queue);
+    g_async_queue_unref (priv->sink_event_queue);
+  }
+
+  if (priv->sink_forward_pad)
+    gst_object_unref (priv->sink_forward_pad);
+
+  gst_object_replace ((GstObject **) & priv->propose_allocator, NULL);
+  gst_object_replace ((GstObject **) & priv->allocator, NULL);
+  gst_object_replace ((GstObject **) & priv->pool, NULL);
+
+  /* if we hold the last ref, set to NULL */
+  if (gst_harness_element_unref (h) == 0) {
+    GstState state, pending;
+    g_assert (gst_element_set_state (h->element, GST_STATE_NULL) ==
+        GST_STATE_CHANGE_SUCCESS);
+    g_assert (gst_element_get_state (h->element, &state, &pending, 0) ==
+        GST_STATE_CHANGE_SUCCESS);
+    g_assert (state == GST_STATE_NULL);
+  }
+
+  g_cond_clear (&priv->blocking_push_cond);
+  g_mutex_clear (&priv->blocking_push_mutex);
+
+  g_ptr_array_unref (priv->stress);
+
+  gst_object_unref (h->element);
+  g_free (h->priv);
+  g_free (h);
+}
+
+/**
+ * gst_harness_add_element_src_pad:
+ * @h: a #GstHarness
+ * @srcpad: a #GstPad to link to the harness sinkpad
+ *
+ * Links the specifed #GstPad the @GstHarness sinkpad. This can be useful if
+ * perhaps the srcpad did not exist at the time of creating the harness,
+ * like a demuxer that provides a sometimes-pad after receiving data.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_element_src_pad (GstHarness * h, GstPad * srcpad)
+{
+  GstHarnessPrivate *priv = h->priv;
+  gst_harness_setup_sink_pad (h, &hsinktemplate, NULL);
+  g_assert_cmpint (gst_pad_link (srcpad, h->sinkpad), ==, GST_PAD_LINK_OK);
+  g_free (priv->element_srcpad_name);
+  priv->element_srcpad_name = gst_pad_get_name (srcpad);
+}
+
+/**
+ * gst_harness_add_element_sink_pad:
+ * @h: a #GstHarness
+ * @sinkpad: a #GstPad to link to the harness srcpad
+ *
+ * Links the specifed #GstPad the @GstHarness srcpad.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_element_sink_pad (GstHarness * h, GstPad * sinkpad)
+{
+  GstHarnessPrivate *priv = h->priv;
+  gst_harness_setup_src_pad (h, &hsrctemplate, NULL);
+  g_assert_cmpint (gst_pad_link (h->srcpad, sinkpad), ==, GST_PAD_LINK_OK);
+  g_free (priv->element_sinkpad_name);
+  priv->element_sinkpad_name = gst_pad_get_name (sinkpad);
+}
+
+/**
+ * gst_harness_set_src_caps:
+ * @h: a #GstHarness
+ * @caps: (transfer full): a #GstCaps to set on the harness srcpad
+ *
+ * Sets the @GstHarness srcpad caps. This must be done before any buffers
+ * can legally be pushed from the harness to the element.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_src_caps (GstHarness * h, GstCaps * caps)
+{
+  GstHarnessPrivate *priv = h->priv;
+  GstSegment segment;
+
+  g_assert (gst_pad_push_event (h->srcpad, gst_event_new_caps (caps)));
+  gst_caps_take (&priv->src_caps, caps);
+
+  gst_segment_init (&segment, GST_FORMAT_TIME);
+  g_assert (gst_pad_push_event (h->srcpad, gst_event_new_segment (&segment)));
+}
+
+/**
+ * gst_harness_set_sink_caps:
+ * @h: a #GstHarness
+ * @caps: (transfer full): a #GstCaps to set on the harness sinkpad
+ *
+ * Sets the @GstHarness sinkpad caps.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_sink_caps (GstHarness * h, GstCaps * caps)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  gst_caps_take (&priv->sink_caps, caps);
+  gst_pad_push_event (h->sinkpad, gst_event_new_reconfigure ());
+}
+
+/**
+ * gst_harness_set_caps:
+ * @h: a #GstHarness
+ * @in: (transfer full): a #GstCaps to set on the harness srcpad
+ * @out: (transfer full): a #GstCaps to set on the harness sinkpad
+ *
+ * Sets the @GstHarness srcpad and sinkpad caps.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_caps (GstHarness * h, GstCaps * in, GstCaps * out)
+{
+  gst_harness_set_sink_caps (h, out);
+  gst_harness_set_src_caps (h, in);
+}
+
+/**
+ * gst_harness_set_src_caps_str:
+ * @h: a #GstHarness
+ * @str: a @gchar describing a #GstCaps to set on the harness srcpad
+ *
+ * Sets the @GstHarness srcpad caps using a string. This must be done before
+ * any buffers can legally be pushed from the harness to the element.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_src_caps_str (GstHarness * h, const gchar * str)
+{
+  gst_harness_set_src_caps (h, gst_caps_from_string (str));
+}
+
+/**
+ * gst_harness_set_sink_caps_str:
+ * @h: a #GstHarness
+ * @str: a @gchar describing a #GstCaps to set on the harness sinkpad
+ *
+ * Sets the @GstHarness sinkpad caps using a string.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_sink_caps_str (GstHarness * h, const gchar * str)
+{
+  gst_harness_set_sink_caps (h, gst_caps_from_string (str));
+}
+
+/**
+ * gst_harness_set_caps_str:
+ * @h: a #GstHarness
+ * @in: a @gchar describing a #GstCaps to set on the harness srcpad
+ * @out: a @gchar describing a #GstCaps to set on the harness sinkpad
+ *
+ * Sets the @GstHarness srcpad and sinkpad caps using strings.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_caps_str (GstHarness * h, const gchar * in, const gchar * out)
+{
+  gst_harness_set_sink_caps_str (h, out);
+  gst_harness_set_src_caps_str (h, in);
+}
+
+/**
+ * gst_harness_use_systemclock:
+ * @h: a #GstHarness
+ *
+ * Sets the system #GstClock on the @GstHarness #GstElement
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_use_systemclock (GstHarness * h)
+{
+  GstClock *clock = gst_system_clock_obtain ();
+  g_assert (clock != NULL);
+  gst_element_set_clock (h->element, clock);
+  gst_object_unref (clock);
+}
+
+/**
+ * gst_harness_use_testclock:
+ * @h: a #GstHarness
+ *
+ * Sets the #GstTestClock on the #GstHarness #GstElement
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_use_testclock (GstHarness * h)
+{
+  GstClock *clock = gst_test_clock_new ();
+  g_assert (clock != NULL);
+  gst_element_set_clock (h->element, clock);
+  gst_object_unref (clock);
+}
+
+/**
+ * gst_harness_get_testclock:
+ * @h: a #GstHarness
+ *
+ * Get the #GstTestClock. Useful if specific operations on the testclock is
+ * needed.
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstTestClock, or %NULL if the testclock is not
+ * present.
+ *
+ * Since: 1.6
+ */
+GstTestClock *
+gst_harness_get_testclock (GstHarness * h)
+{
+  GstTestClock *testclock = NULL;
+  GstClock *clock;
+
+  clock = gst_element_get_clock (h->element);
+  if (clock) {
+    if (GST_IS_TEST_CLOCK (clock))
+      testclock = GST_TEST_CLOCK (clock);
+    else
+      gst_object_unref (clock);
+  }
+  return testclock;
+}
+
+/**
+ * gst_harness_set_time:
+ * @h: a #GstHarness
+ * @time: a #GstClockTime to advance the clock to
+ *
+ * Advance the #GstTestClock to a specific time.
+ *
+ * MT safe.
+ *
+ * Returns: a @gboolean %TRUE if the time could be set. %FALSE if not.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_set_time (GstHarness * h, GstClockTime time)
+{
+  GstTestClock *testclock;
+  testclock = gst_harness_get_testclock (h);
+  if (testclock == NULL)
+    return FALSE;
+
+  gst_test_clock_set_time (testclock, time);
+  gst_object_unref (testclock);
+  return TRUE;
+}
+
+/**
+ * gst_harness_wait_for_clock_id_waits:
+ * @h: a #GstHarness
+ * @waits: a #guint describing the numbers of #GstClockID registered with
+ * the #GstTestClock
+ * @timeout: a #guint describing how many seconds to wait for @waits to be true
+ *
+ * Waits for @timeout seconds until @waits number of #GstClockID waits is
+ * registered with the #GstTestClock. Useful for writing deterministic tests,
+ * where you want to make sure that an expected number of waits have been
+ * reached.
+ *
+ * MT safe.
+ *
+ * Returns: a @gboolean %TRUE if the waits have been registered, %FALSE if not.
+ * (Could be that it timed out waiting or that more waits then waits was found)
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_wait_for_clock_id_waits (GstHarness * h, guint waits, guint timeout)
+{
+  GstTestClock *testclock = gst_harness_get_testclock (h);
+  gint64 start_time;
+  gboolean ret;
+
+  if (testclock == NULL)
+    return FALSE;
+
+  start_time = g_get_monotonic_time ();
+  while (gst_test_clock_peek_id_count (testclock) < waits) {
+    gint64 time_spent;
+
+    g_usleep (G_USEC_PER_SEC / 1000);
+    time_spent = g_get_monotonic_time () - start_time;
+    if ((time_spent / G_USEC_PER_SEC) > timeout)
+      break;
+  }
+
+  ret = (waits == gst_test_clock_peek_id_count (testclock));
+
+  gst_object_unref (testclock);
+  return ret;
+}
+
+/**
+ * gst_harness_crank_single_clock_wait:
+ * @h: a #GstHarness
+ *
+ * A "crank" consists of three steps:
+ * 1: Wait for a #GstClockID to be registered with the #GstTestClock.
+ * 2: Advance the #GstTestClock to the time the #GstClockID is waiting for.
+ * 3: Release the #GstClockID wait.
+ * Together, this provides an easy way to not have to think about the details
+ * around clocks and time, but still being able to write deterministic tests
+ * that are dependant on this. A "crank" can be though of as the notion of
+ * manually driving the clock forward to its next logical step.
+ *
+ * MT safe.
+ *
+ * Returns: a @gboolean %TRUE if the "crank" was successful, %FALSE if not.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_crank_single_clock_wait (GstHarness * h)
+{
+  GstTestClock *testclock = gst_harness_get_testclock (h);
+  GstClockID res, pending;
+  gboolean ret = FALSE;
+
+  if (G_LIKELY (testclock != NULL)) {
+    gst_test_clock_wait_for_next_pending_id (testclock, &pending);
+
+    gst_test_clock_set_time (testclock, gst_clock_id_get_time (pending));
+    res = gst_test_clock_process_next_clock_id (testclock);
+    if (res == pending) {
+      GST_DEBUG ("cranked time %" GST_TIME_FORMAT,
+          GST_TIME_ARGS (gst_clock_get_time (GST_CLOCK (testclock))));
+      ret = TRUE;
+    } else {
+      GST_WARNING ("testclock next id != pending (%p != %p)", res, pending);
+    }
+
+    gst_clock_id_unref (res);
+    gst_clock_id_unref (pending);
+
+    gst_object_unref (testclock);
+  } else {
+    GST_WARNING ("No testclock on element %s", GST_ELEMENT_NAME (h->element));
+  }
+
+  return ret;
+}
+
+/**
+ * gst_harness_crank_multiple_clock_waits:
+ * @h: a #GstHarness
+ * @waits: a #guint describing the number of #GstClockIDs to crank
+ *
+ * Similar to gst_harness_crank_single_clock_wait, this is the function to use
+ * if your harnessed element(s) are using more then one gst_clock_id_wait.
+ * Failing to do so can (and will) make it racy which #GstClockID you actually
+ * are releasing, where as this function will process all the waits at the
+ * same time, ensuring that one thread can't register another wait before
+ * both are released.
+ *
+ * MT safe.
+ *
+ * Returns: a @gboolean %TRUE if the "crank" was successful, %FALSE if not.
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_crank_multiple_clock_waits (GstHarness * h, guint waits)
+{
+  GstTestClock *testclock;
+  GList *pending;
+  guint processed;
+
+  testclock = gst_harness_get_testclock (h);
+  if (testclock == NULL)
+    return FALSE;
+
+  gst_test_clock_wait_for_multiple_pending_ids (testclock, waits, &pending);
+  gst_harness_set_time (h, gst_test_clock_id_list_get_latest_time (pending));
+  processed = gst_test_clock_process_id_list (testclock, pending);
+
+  g_list_free_full (pending, gst_clock_id_unref);
+  gst_object_unref (testclock);
+  return processed == waits;
+}
+
+/**
+ * gst_harness_play:
+ * @h: a #GstHarness
+ *
+ * This will set the harnessed #GstElement to %GST_STATE_PLAYING.
+ * #GstElements without a sink-#GstPad and with the %GST_ELEMENT_FLAG_SOURCE
+ * flag set is concidered a src #GstElement
+ * Non-src #GstElements (like sinks and filters) are automatically set to
+ * playing by the #GstHarness, but src #GstElements are not to avoid them
+ * starting to produce buffers.
+ * Hence, for src #GstElement you will need to call gst_harness_play explicitly.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_play (GstHarness * h)
+{
+  GstState state, pending;
+  g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==,
+      gst_element_set_state (h->element, GST_STATE_PLAYING));
+  g_assert_cmpint (GST_STATE_CHANGE_SUCCESS, ==,
+      gst_element_get_state (h->element, &state, &pending, 0));
+  g_assert_cmpint (GST_STATE_PLAYING, ==, state);
+}
+
+/**
+ * gst_harness_set_blocking_push_mode:
+ * @h: a #GstHarness
+ *
+ * Setting this will make the harness block in the chain-function, and
+ * then release when gst_harness_pull or gst_harness_try_pull is called.
+ * Can be useful when wanting to control a src-element that is not implementing
+ * gst_clock_id_wait so it can't be controlled by the #GstTestClock, since
+ * it otherwise would produce buffers as fast as possible.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_blocking_push_mode (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  priv->blocking_push_mode = TRUE;
+}
+
+/**
+ * gst_harness_create_buffer:
+ * @h: a #GstHarness
+ * @size: a #gsize specifying the size of the buffer
+ *
+ * Allocates a buffer using a #GstBufferPool if present, or else using the
+ * configured #GstAllocator and #GstAllocatorParams
+ *
+ * MT safe.
+ *
+ * Returns: a #GstBuffer of size @size
+ *
+ * Since: 1.6
+ */
+GstBuffer *
+gst_harness_create_buffer (GstHarness * h, gsize size)
+{
+  GstHarnessPrivate *priv = h->priv;
+  GstBuffer *ret = NULL;
+
+  if (gst_pad_check_reconfigure (h->srcpad))
+    gst_harness_negotiate (h);
+
+  if (priv->pool) {
+    g_assert_cmpint (gst_buffer_pool_acquire_buffer (priv->pool, &ret, NULL), ==,
+        GST_FLOW_OK);
+    if (gst_buffer_get_size (ret) != size) {
+      GST_DEBUG_OBJECT (h,
+          "use fallback, pool is configured with a different size (%zu != %zu)",
+          size, gst_buffer_get_size (ret));
+      gst_buffer_unref (ret);
+      ret = NULL;
+    }
+  }
+
+  if (!ret)
+    ret = gst_buffer_new_allocate (priv->allocator, size, &priv->allocation_params);
+
+  g_assert (ret != NULL);
+  return ret;
+}
+
+/**
+ * gst_harness_push:
+ * @h: a #GstHarness
+ * @buffer: a #GstBuffer to push
+ *
+ * Pushes a #GstBuffer on the #GstHarness srcpad. The standard way of
+ * interacting with an harnessed element.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstFlowReturn with the result from the push
+ *
+ * Since: 1.6
+ */
+GstFlowReturn
+gst_harness_push (GstHarness * h, GstBuffer * buffer)
+{
+  GstHarnessPrivate *priv = h->priv;
+  g_assert (buffer != NULL);
+  priv->last_push_ts = GST_BUFFER_TIMESTAMP (buffer);
+  return gst_pad_push (h->srcpad, buffer);
+}
+
+/**
+ * gst_harness_pull:
+ * @h: a #GstHarness
+ *
+ * Pulls a #GstBuffer from the #GAsyncQueue on the #GstHarness sinkpad. The pull
+ * will timeout in 60 seconds. This is the standard way of getting a buffer
+ * from a harnessed #GstElement.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstBuffer or %NULL if timed out.
+ *
+ * Since: 1.6
+ */
+GstBuffer *
+gst_harness_pull (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  if (priv->blocking_push_mode) {
+    g_mutex_lock (&priv->blocking_push_mutex);
+    g_cond_signal (&priv->blocking_push_cond);
+    g_mutex_unlock (&priv->blocking_push_mutex);
+  }
+
+  return (GstBuffer *) g_async_queue_timeout_pop (priv->buffer_queue,
+      G_USEC_PER_SEC * 60);
+}
+
+/**
+ * gst_harness_try_pull:
+ * @h: a #GstHarness
+ *
+ * Pulls a #GstBuffer from the #GAsyncQueue on the #GstHarness sinkpad. Unlike
+ * gst_harness_pull this will not wait for any buffers if not any are present,
+ * and return %NULL straight away.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstBuffer or %NULL if no buffers are present in the #GAsyncQueue
+ *
+ * Since: 1.6
+ */
+GstBuffer *
+gst_harness_try_pull (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  if (priv->blocking_push_mode) {
+    g_mutex_lock (&priv->blocking_push_mutex);
+    g_cond_signal (&priv->blocking_push_cond);
+    g_mutex_unlock (&priv->blocking_push_mutex);
+  }
+
+  return (GstBuffer *) g_async_queue_try_pop (priv->buffer_queue);
+}
+
+/**
+ * gst_harness_push_and_pull:
+ * @h: a #GstHarness
+ * @buffer: a #GstBuffer to push
+ *
+ * Basically a gst_harness_push and a gst_harness_pull in one line. Reflects
+ * the fact that you often want to do exactly this in your test: Push one buffer
+ * in, and inspect the outcome.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstBuffer or %NULL if timed out.
+ *
+ * Since: 1.6
+ */
+GstBuffer *
+gst_harness_push_and_pull (GstHarness * h, GstBuffer * buffer)
+{
+  gst_harness_push (h, buffer);
+  return gst_harness_pull (h);
+}
+
+/**
+ * gst_harness_buffers_received:
+ * @h: a #GstHarness
+ *
+ * The total number of #GstBuffers that has arrived on the #GstHarness sinkpad.
+ * This number includes buffers that have been dropped as well as buffers
+ * that have already been pulled out.
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of buffers received
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_buffers_received (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_atomic_int_get (&priv->recv_buffers);
+}
+
+/**
+ * gst_harness_buffers_in_queue:
+ * @h: a #GstHarness
+ *
+ * The number of #GstBuffers currently in the #GstHarness sinkpad #GAsyncQueue
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of buffers in the queue
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_buffers_in_queue (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_async_queue_length (priv->buffer_queue);
+}
+
+/**
+ * gst_harness_set_drop_buffers:
+ * @h: a #GstHarness
+ * @drop_buffers: a #gboolean specifying to drop outgoing buffers or not
+ *
+ * When set to %TRUE, instead of placing the buffers arriving from the harnessed
+ * #GstElement inside the sinkpads #GAsyncQueue, they are instead unreffed.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_drop_buffers (GstHarness * h, gboolean drop_buffers)
+{
+  GstHarnessPrivate *priv = h->priv;
+  priv->drop_buffers = drop_buffers;
+}
+
+/**
+ * gst_harness_dump_to_file:
+ * @h: a #GstHarness
+ * @filename: a #gchar with a the name of a file
+ *
+ * Allows you to dump the #GstBuffers the #GstHarness sinkpad #GAsyncQueue
+ * to a file.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_dump_to_file (GstHarness * h, const gchar * filename)
+{
+  GstHarnessPrivate *priv = h->priv;
+  FILE *fd;
+  GstBuffer *buf;
+  fd = fopen (filename, "wb");
+  g_assert (fd);
+
+  while ((buf = g_async_queue_try_pop (priv->buffer_queue))) {
+    GstMapInfo info;
+    gst_buffer_map (buf, &info, GST_MAP_READ);
+    fwrite (info.data, 1, info.size, fd);
+    gst_buffer_unmap (buf, &info);
+    gst_buffer_unref (buf);
+  }
+
+  fflush (fd);
+  fclose (fd);
+}
+
+/**
+ * gst_harness_get_last_pushed_timestamp:
+ * @h: a #GstHarness
+ *
+ * Get the timestamp of the last #GstBuffer pushed on the #GstHarness srcpad,
+ * typically with gst_harness_push or gst_harness_push_from_src.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstClockTime with the timestamp or %GST_CLOCK_TIME_NONE if no
+ * #GstBuffer has been pushed on the #GstHarness srcpad
+ *
+ * Since: 1.6
+ */
+GstClockTime
+gst_harness_get_last_pushed_timestamp (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return priv->last_push_ts;
+}
+
+/**
+ * gst_harness_push_event:
+ * @h: a #GstHarness
+ * @event: a #GstEvent to push
+ *
+ * Pushes an #GstEvent on the #GstHarness srcpad.
+ *
+ * MT safe.
+ *
+ * Returns: a #gboolean with the result from the push
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_push_event (GstHarness * h, GstEvent * event)
+{
+  return gst_pad_push_event (h->srcpad, event);
+}
+
+/**
+ * gst_harness_pull_event:
+ * @h: a #GstHarness
+ *
+ * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness sinkpad.
+ * Timeouts after 60 seconds similar to gst_harness_pull.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstEvent or %NULL if timed out.
+ *
+ * Since: 1.6
+ */
+GstEvent *
+gst_harness_pull_event (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return (GstEvent *) g_async_queue_timeout_pop (priv->sink_event_queue,
+      G_USEC_PER_SEC * 60);
+}
+
+/**
+ * gst_harness_try_pull_event:
+ * @h: a #GstHarness
+ *
+ * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness sinkpad.
+ * See gst_harness_try_pull for details.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstEvent or %NULL if no buffers are present in the #GAsyncQueue
+ *
+ * Since: 1.6
+ */
+GstEvent *
+gst_harness_try_pull_event (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return (GstEvent *) g_async_queue_try_pop (priv->sink_event_queue);
+}
+
+/**
+ * gst_harness_events_received:
+ * @h: a #GstHarness
+ *
+ * The total number of #GstEvents that has arrived on the #GstHarness sinkpad
+ * This number includes events handled by the harness as well as events
+ * that have already been pulled out.
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of events received
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_events_received (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_atomic_int_get (&priv->recv_events);
+}
+
+/**
+ * gst_harness_events_in_queue:
+ * @h: a #GstHarness
+ *
+ * The number of #GstEvents currently in the #GstHarness sinkpad #GAsyncQueue
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of events in the queue
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_events_in_queue (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_async_queue_length (priv->sink_event_queue);
+}
+
+/**
+ * gst_harness_push_upstream_event:
+ * @h: a #GstHarness
+ * @event: a #GstEvent to push
+ *
+ * Pushes an #GstEvent on the #GstHarness sinkpad.
+ *
+ * MT safe.
+ *
+ * Returns: a #gboolean with the result from the push
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_push_upstream_event (GstHarness * h, GstEvent * event)
+{
+  g_return_val_if_fail (event != NULL, FALSE);
+  g_return_val_if_fail (GST_EVENT_IS_UPSTREAM (event), FALSE);
+
+  return gst_pad_push_event (h->sinkpad, event);
+}
+
+/**
+ * gst_harness_pull_upstream_event:
+ * @h: a #GstHarness
+ *
+ * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness srcpad.
+ * Timeouts after 60 seconds similar to gst_harness_pull.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstEvent or %NULL if timed out.
+ *
+ * Since: 1.6
+ */
+GstEvent *
+gst_harness_pull_upstream_event (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return (GstEvent *) g_async_queue_timeout_pop (priv->src_event_queue,
+      G_USEC_PER_SEC * 60);
+}
+
+/**
+ * gst_harness_try_pull_upstream_event:
+ * @h: a #GstHarness
+ *
+ * Pulls an #GstEvent from the #GAsyncQueue on the #GstHarness srcpad.
+ * See gst_harness_try_pull for details.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstEvent or %NULL if no buffers are present in the #GAsyncQueue
+ *
+ * Since: 1.6
+ */
+GstEvent *
+gst_harness_try_pull_upstream_event (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return (GstEvent *) g_async_queue_try_pop (priv->src_event_queue);
+}
+
+/**
+ * gst_harness_upstream_events_received:
+ * @h: a #GstHarness
+ *
+ * The total number of #GstEvents that has arrived on the #GstHarness srcpad
+ * This number includes events handled by the harness as well as events
+ * that have already been pulled out.
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of events received
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_upstream_events_received (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_atomic_int_get (&priv->recv_upstream_events);
+}
+
+/**
+ * gst_harness_upstream_events_in_queue:
+ * @h: a #GstHarness
+ *
+ * The number of #GstEvents currently in the #GstHarness srcpad #GAsyncQueue
+ *
+ * MT safe.
+ *
+ * Returns: a #guint number of events in the queue
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_upstream_events_in_queue (GstHarness * h)
+{
+  GstHarnessPrivate *priv = h->priv;
+  return g_async_queue_length (priv->src_event_queue);
+}
+
+/**
+ * gst_harness_query_latency:
+ * @h: a #GstHarness
+ *
+ * Get the min latency reported by any harnessed #GstElement.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstClockTime with min latency
+ *
+ * Since: 1.6
+ */
+GstClockTime
+gst_harness_query_latency (GstHarness * h)
+{
+  GstQuery *query;
+  gboolean is_live;
+  GstClockTime min = GST_CLOCK_TIME_NONE;
+  GstClockTime max;
+
+  query = gst_query_new_latency ();
+
+  if (gst_pad_peer_query (h->sinkpad, query)) {
+    gst_query_parse_latency (query, &is_live, &min, &max);
+  }
+  gst_query_unref (query);
+
+  return min;
+}
+
+/**
+ * gst_harness_set_upstream_latency:
+ * @h: a #GstHarness
+ * @latency: a #GstClockTime specifying the latency
+ *
+ * Sets the min latency reported by #GstHarness when receiving a latency-query
+ *
+ * MT safe.
+ *
+ * Returns: a #GstClockTime with min latency
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_upstream_latency (GstHarness * h, GstClockTime latency)
+{
+  GstHarnessPrivate *priv = h->priv;
+  priv->latency_min = latency;
+}
+
+/**
+ * gst_harness_get_allocator:
+ * @h: a #GstHarness
+ * @allocator: (out) (allow-none) (transfer none): the #GstAllocator
+ * used
+ * @params: (out) (allow-none) (transfer full): the
+ * #GstAllocatorParams of @allocator
+ *
+ * Gets the @allocator and its @params that has been decided to use after an
+ * allocation query.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_get_allocator (GstHarness * h, GstAllocator ** allocator,
+    GstAllocationParams * params)
+{
+  GstHarnessPrivate *priv = h->priv;
+  if (allocator)
+    *allocator = priv->allocator;
+  if (params)
+    *params = priv->allocation_params;
+}
+
+
+/**
+ * gst_harness_get_allocator:
+ * @h: a #GstHarness
+ * @allocator: (allow-none) (transfer full): a #GstAllocator
+ * @params: (allow-none) (transfer none): a #GstAllocationParams
+ *
+ * Sets the @allocator and @params to propose when receiving an allocation
+ * query.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set_propose_allocator (GstHarness * h, GstAllocator * allocator,
+    const GstAllocationParams * params)
+{
+  GstHarnessPrivate *priv = h->priv;
+  if (allocator)
+    priv->propose_allocator = allocator;
+  if (params)
+    priv->propose_allocation_params = *params;
+}
+
+static void
+gst_harness_setup_src_harness (GstHarness * h, gboolean has_clock_wait)
+{
+  h->src_harness->priv->sink_forward_pad = gst_object_ref (h->srcpad);
+  gst_harness_use_testclock (h->src_harness);
+  h->src_harness->priv->has_clock_wait = has_clock_wait;
+}
+
+/**
+ * gst_harness_add_src:
+ * @h: a #GstHarness
+ * @src_element_name: a #gchar with the name of a #GstElement
+ * @has_clock_wait: a #gboolean specifying if the #GstElement uses
+ * gst_clock_wait_id internally.
+ *
+ * A src-harness is a great way of providing the #GstHarness with data.
+ * By adding a src-type #GstElement, it is then easy to use functions like
+ * gst_harness_push_from_src or gst_harness_src_crank_and_push_many
+ * to provide your harnessed element with input. The @has_clock_wait variable
+ * is a greate way to control you src-element with, in that you can have it
+ * produce a buffer for you by simply cranking the clock, and not have it
+ * spin out of control producing buffers as fast as possible.
+ *
+ * If a src-harness already exists it will be replaced.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_src (GstHarness * h,
+    const gchar * src_element_name, gboolean has_clock_wait)
+{
+  if (h->src_harness)
+    gst_harness_teardown (h->src_harness);
+  h->src_harness = gst_harness_new (src_element_name);
+  gst_harness_setup_src_harness (h, has_clock_wait);
+}
+
+/**
+ * gst_harness_add_src_parse:
+ * @h: a #GstHarness
+ * @launchline: a #gchar describing a gst-launch type line
+ * @has_clock_wait: a #gboolean specifying if the #GstElement uses
+ * gst_clock_wait_id internally.
+ *
+ * Similar to gst_harness_add_src, this allows you to specify a launch-line,
+ * which can be useful for both having more then one #GstElement acting as your
+ * src (Like a src producing raw buffers, and then an encoder, providing encoded
+ * data), but also by allowing you to set properties like "is-live" directly on
+ * the elements.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_src_parse (GstHarness * h,
+    const gchar * launchline, gboolean has_clock_wait)
+{
+  if (h->src_harness)
+    gst_harness_teardown (h->src_harness);
+  h->src_harness = gst_harness_new_parse (launchline);
+  gst_harness_setup_src_harness (h, has_clock_wait);
+}
+
+/**
+ * gst_harness_push_from_src:
+ * @h: a #GstHarness
+ *
+ * Transfer data from the src-#GstHarness to the main-#GstHarness. It consists
+ * of 4 steps:
+ * 1: Make sure the src is started. (see: gst_harness_play)
+ * 2: Crank the clock (see: gst_harness_crank_single_clock_wait)
+ * 3: Pull a #GstBuffer from the src-#GstHarness (see: gst_harness_pull)
+ * 4: Push the same #GstBuffer into the main-#GstHarness (see: gst_harness_push)
+ *
+ * MT safe.
+ *
+ * Returns: a #GstFlowReturn with the result of the push
+ *
+ * Since: 1.6
+ */
+GstFlowReturn
+gst_harness_push_from_src (GstHarness * h)
+{
+  GstBuffer *buf;
+
+  g_assert (h->src_harness);
+
+  /* FIXME: this *is* the right time to start the src,
+     but maybe a flag so we don't keep telling it to play? */
+  gst_harness_play (h->src_harness);
+
+  if (h->src_harness->priv->has_clock_wait) {
+    g_assert (gst_harness_crank_single_clock_wait (h->src_harness));
+  }
+
+  g_assert ((buf = gst_harness_pull (h->src_harness)) != NULL);
+  return gst_harness_push (h, buf);
+}
+
+/**
+ * gst_harness_src_crank_and_push_many:
+ * @h: a #GstHarness
+ * @cranks: a #gint with the number of calls to gst_harness_crank_single_clock_wait
+ * @pushes: a #gint with the number of calls to gst_harness_push
+ *
+ * Transfer data from the src-#GstHarness to the main-#GstHarness. Similar to
+ * gst_harness_push_from_src, this variant allows you to specify how many cranks
+ * and how many pushes to perform. This can be useful for both moving a lot
+ * of data at the same time, as well as cases when one crank does not equal one
+ * buffer to push and v.v.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstFlowReturn with the result of the push
+ *
+ * Since: 1.6
+ */
+GstFlowReturn
+gst_harness_src_crank_and_push_many (GstHarness * h, gint cranks, gint pushes)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  g_assert (h->src_harness);
+  gst_harness_play (h->src_harness);
+
+  for (int i = 0; i < cranks; i++)
+    g_assert (gst_harness_crank_single_clock_wait (h->src_harness));
+
+  for (int i = 0; i < pushes; i++) {
+    GstBuffer *buf;
+    g_assert ((buf = gst_harness_pull (h->src_harness)) != NULL);
+    ret = gst_harness_push (h, buf);
+    if (ret != GST_FLOW_OK)
+      break;
+  }
+
+  return ret;
+}
+
+/**
+ * gst_harness_src_push_event:
+ * @h: a #GstHarness
+ *
+ * Similar to what gst_harness_src_push does with #GstBuffers, this transfers
+ * a #GstEvent from the src-#GstHarness to the main-#GstHarness. Note that
+ * some #GstEvents are being transferred automagically. Look at sink_forward_pad
+ * for details.
+ *
+ * MT safe.
+ *
+ * Returns: a #gboolean with the result of the push
+ *
+ * Since: 1.6
+ */
+gboolean
+gst_harness_src_push_event (GstHarness * h)
+{
+  return gst_harness_push_event (h, gst_harness_pull_event (h->src_harness));
+}
+
+/**
+ * gst_harness_add_sink:
+ * @h: a #GstHarness
+ * @sink_element_name: a #gchar with the name of a #GstElement
+ *
+ * Similar to gst_harness_add_src, this allows you to send the data coming out
+ * of your harnessed #GstElement to a sink-element, allowing to test different
+ * responses the element output might create in sink elements. An example might
+ * be an existing sink providing some analytical data on the input it receives that
+ * can be useful to your testing. If the goal is to test a sink-element itself,
+ * this is better acheived using gst_harness_new directly on the sink.
+ *
+ * If a sink-harness already exists it will be replaced.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_sink (GstHarness * h, const gchar * sink_element_name)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  if (h->sink_harness) {
+    gst_harness_teardown (h->sink_harness);
+    gst_object_unref (priv->sink_forward_pad);
+  }
+  h->sink_harness = gst_harness_new (sink_element_name);
+  priv->sink_forward_pad = gst_object_ref (h->sink_harness->srcpad);
+}
+
+/**
+ * gst_harness_add_sink_parse:
+ * @h: a #GstHarness
+ * @launchline: a #gchar with the name of a #GstElement
+ *
+ * Similar to gst_harness_add_sink, this allows you to specify a launch-line
+ * instead of just an element name. See gst_harness_add_src_parse for details.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_sink_parse (GstHarness * h, const gchar * launchline)
+{
+  GstHarnessPrivate *priv = h->priv;
+
+  if (h->sink_harness) {
+    gst_harness_teardown (h->sink_harness);
+    gst_object_unref (priv->sink_forward_pad);
+  }
+  h->sink_harness = gst_harness_new_parse (launchline);
+  priv->sink_forward_pad = gst_object_ref (h->sink_harness->srcpad);
+}
+
+/**
+ * gst_harness_push_to_sink:
+ * @h: a #GstHarness
+ *
+ * Transfer one #GstBuffer from the main-#GstHarness to the sink-#GstHarness.
+ * See gst_harness_push_from_src for details.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstFlowReturn with the result of the push
+ *
+ * Since: 1.6
+ */
+GstFlowReturn
+gst_harness_push_to_sink (GstHarness * h)
+{
+  GstBuffer *buf;
+  g_assert (h->sink_harness);
+  g_assert ((buf = gst_harness_pull (h)) != NULL);
+  return gst_harness_push (h->sink_harness, buf);
+}
+
+/**
+ * gst_harness_sink_push_many:
+ * @h: a #GstHarness
+ * @pushes: a #gint with the number of calls to gst_harness_push_to_sink
+ *
+ * Convenience that calls gst_harness_push_to_sink @pushes number of times.
+ * Will abort the pushing if any one push fails.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstFlowReturn with the result of the push
+ *
+ * Since: 1.6
+ */
+GstFlowReturn
+gst_harness_sink_push_many (GstHarness * h, gint pushes)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  g_assert (h->sink_harness);
+  for (int i = 0; i < pushes; i++) {
+    ret = gst_harness_push_to_sink (h);
+    if (ret != GST_FLOW_OK)
+      break;
+  }
+  return ret;
+}
+
+/**
+ * gst_harness_find_element:
+ * @h: a #GstHarness
+ * @element_name: a #gchar with a #GstElementFactory name
+ *
+ * Most useful in conjunction with gst_harness_new_parse, this will scan the
+ * #GstElements inside the #GstHarness, and check if any of them matches
+ * @element_name. Typical usecase being that you need to access one of the
+ * harnessed elements for properties and/or signals.
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full) (allow-none): a #GstElement or %NULL if not found
+ *
+ * Since: 1.6
+ */
+GstElement *
+gst_harness_find_element (GstHarness * h, const gchar * element_name)
+{
+  gboolean done = FALSE;
+  GstIterator *iter;
+  GValue data = G_VALUE_INIT;
+
+  iter = gst_bin_iterate_elements (GST_BIN (h->element));
+  done = FALSE;
+
+  while (!done) {
+    switch (gst_iterator_next (iter, &data)) {
+      case GST_ITERATOR_OK:
+      {
+        GstElement *element = g_value_get_object (&data);
+        GstPluginFeature *feature =
+            GST_PLUGIN_FEATURE (gst_element_get_factory (element));
+        if (!strcmp (element_name, gst_plugin_feature_get_name (feature))) {
+          gst_iterator_free (iter);
+          return element;
+        }
+        g_value_reset (&data);
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (iter);
+        break;
+      case GST_ITERATOR_ERROR:
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+    }
+  }
+  gst_iterator_free (iter);
+
+  return NULL;
+}
+
+/**
+ * gst_harness_set:
+ * @h: a #GstHarness
+ * @element_name: a #gchar with a #GstElementFactory name
+ * @first_property_name: a #gchar with the first property name
+ *
+ * A convenience function to allows you to call g_object_set on a #GstElement
+ * that are residing inside the #GstHarness, by using normal g_object_set
+ * syntax.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_set (GstHarness * h,
+    const gchar * element_name, const gchar * first_property_name, ...)
+{
+  va_list var_args;
+  GstElement *element = gst_harness_find_element (h, element_name);
+  va_start (var_args, first_property_name);
+  g_object_set_valist (G_OBJECT (element), first_property_name, var_args);
+  va_end (var_args);
+  gst_object_unref (element);
+}
+
+/**
+ * gst_harness_get:
+ * @h: a #GstHarness
+ * @element_name: a #gchar with a #GstElementFactory name
+ * @first_property_name: a #gchar with the first property name
+ *
+ * A convenience function to allows you to call g_object_get on a #GstElement
+ * that are residing inside the #GstHarness, by using normal g_object_get
+ * syntax.
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_get (GstHarness * h,
+    const gchar * element_name, const gchar * first_property_name, ...)
+{
+  va_list var_args;
+  GstElement *element = gst_harness_find_element (h, element_name);
+  va_start (var_args, first_property_name);
+  g_object_get_valist (G_OBJECT (element), first_property_name, var_args);
+  va_end (var_args);
+  gst_object_unref (element);
+}
+
+/**
+ * gst_harness_add_probe:
+ * @h: a #GstHarness
+ * @element_name: a #gchar with a #GstElementFactory name
+ * @pad_name: a #gchar with the name of the pad to attach the probe to
+ * @mask: a #GstPadProbeType (see gst_pad_add_probe)
+ * @callback: a #GstPadProbeCallback (see gst_pad_add_probe)
+ * @user_data: a #gpointer (see gst_pad_add_probe)
+ * @destroy_data: a #GDestroyNotify (see gst_pad_add_probe)
+ *
+ * A convenience function to allows you to call gst_pad_add_probe on a
+ * #GstPad of a #GstElement that are residing inside the #GstHarness,
+ * by using normal gst_pad_add_probe syntax
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+void
+gst_harness_add_probe (GstHarness * h,
+    const gchar * element_name, const gchar * pad_name, GstPadProbeType mask,
+    GstPadProbeCallback callback, gpointer user_data,
+    GDestroyNotify destroy_data)
+{
+  GstElement *element = gst_harness_find_element (h, element_name);
+  GstPad *pad = gst_element_get_static_pad (element, pad_name);
+  gst_pad_add_probe (pad, mask, callback, user_data, destroy_data);
+  gst_object_unref (pad);
+  gst_object_unref (element);
+}
+
+/******************************************************************************/
+/*       STRESS                                                               */
+/******************************************************************************/
+struct _GstHarnessThread
+{
+  GstHarness *h;
+  GThread *thread;
+  gboolean running;
+
+  gulong sleep;
+
+  GDestroyNotify freefunc;
+};
+
+typedef struct
+{
+  GstHarnessThread t;
+
+  GFunc init;
+  GFunc callback;
+  gpointer data;
+} GstHarnessCustomThread;
+
+typedef struct
+{
+  GstHarnessThread t;
+
+  GstCaps *caps;
+  GstSegment segment;
+  GstHarnessPrepareBuffer func;
+  gpointer data;
+  GDestroyNotify notify;
+} GstHarnessPushBufferThread;
+
+typedef struct
+{
+  GstHarnessThread t;
+
+  GstEvent *event;
+} GstHarnessPushEventThread;
+
+typedef struct
+{
+  GstHarnessThread t;
+
+  gchar *name;
+  GValue value;
+} GstHarnessPropThread;
+
+typedef struct
+{
+  GstHarnessThread t;
+
+  GstPadTemplate *templ;
+  gchar *name;
+  GstCaps *caps;
+  gboolean release;
+
+  GSList *pads;
+} GstHarnessReqPadThread;
+
+static void
+gst_harness_thread_init (GstHarnessThread * t, GDestroyNotify freefunc,
+    GstHarness * h, gulong sleep)
+{
+  t->freefunc = freefunc;
+  t->h = h;
+  t->sleep = sleep;
+
+  g_ptr_array_add (h->priv->stress, t);
+}
+
+static void
+gst_harness_thread_free (GstHarnessThread * t)
+{
+  g_slice_free (GstHarnessThread, t);
+}
+
+static void
+gst_harness_custom_thread_free (GstHarnessCustomThread * t)
+{
+  g_slice_free (GstHarnessCustomThread, t);
+}
+
+static void
+gst_harness_push_buffer_thread_free (GstHarnessPushBufferThread * t)
+{
+  if (t != NULL) {
+    gst_caps_replace (&t->caps, NULL);
+    if (t->notify != NULL)
+      t->notify (t->data);
+    g_slice_free (GstHarnessPushBufferThread, t);
+  }
+}
+
+static void
+gst_harness_push_event_thread_free (GstHarnessPushEventThread * t)
+{
+  if (t != NULL) {
+    gst_event_replace (&t->event, NULL);
+    g_slice_free (GstHarnessPushEventThread, t);
+  }
+}
+
+static void
+gst_harness_property_thread_free (GstHarnessPropThread * t)
+{
+  if (t != NULL) {
+    g_free (t->name);
+    g_value_unset (&t->value);
+    g_slice_free (GstHarnessPropThread, t);
+  }
+}
+
+static void
+gst_harness_requestpad_release (GstPad * pad, GstElement * element)
+{
+  gst_element_release_request_pad (element, pad);
+  gst_object_unref (pad);
+}
+
+static void
+gst_harness_requestpad_release_pads (GstHarnessReqPadThread * rpt)
+{
+  g_slist_foreach (rpt->pads, (GFunc) gst_harness_requestpad_release,
+      rpt->t.h->element);
+  g_slist_free (rpt->pads);
+  rpt->pads = NULL;
+}
+
+static void
+gst_harness_requestpad_thread_free (GstHarnessReqPadThread * t)
+{
+  if (t != NULL) {
+    gst_object_replace ((GstObject **) & t->templ, NULL);
+    g_free (t->name);
+    gst_caps_replace (&t->caps, NULL);
+
+    gst_harness_requestpad_release_pads (t);
+    g_slice_free (GstHarnessReqPadThread, t);
+  }
+}
+
+#define GST_HARNESS_THREAD_START(ID, t)                                        \
+  (((GstHarnessThread *)t)->running = TRUE,                                    \
+  ((GstHarnessThread *)t)->thread = g_thread_new (                             \
+      "gst-harness-stress-"G_STRINGIFY(ID),                                    \
+      (GThreadFunc)gst_harness_stress_##ID##_func, t))
+#define GST_HARNESS_THREAD_END(t)                                              \
+   (t->running = FALSE,                                                        \
+   GPOINTER_TO_UINT (g_thread_join (t->thread)))
+
+#define GST_HARNESS_STRESS_FUNC_BEGIN(ID, INIT)                                \
+  static gpointer                                                              \
+  gst_harness_stress_##ID##_func (GstHarnessThread * t)                        \
+  {                                                                            \
+    guint count = 0;                                                           \
+    INIT;                                                                      \
+                                                                               \
+    while (t->running) {
+
+#define GST_HARNESS_STRESS_FUNC_END()                                          \
+      count++;                                                                 \
+      g_usleep (t->sleep);                                                     \
+    }                                                                          \
+    return GUINT_TO_POINTER (count);                                           \
+  }
+
+static void
+gst_harness_stress_free (GstHarnessThread * t)
+{
+  if (t != NULL && t->freefunc != NULL)
+    t->freefunc (t);
+}
+
+GST_HARNESS_STRESS_FUNC_BEGIN (custom, {
+    GstHarnessCustomThread *ct = (GstHarnessCustomThread *) t;
+    ct->init (ct, ct->data);
+  }
+)
+{
+  GstHarnessCustomThread *ct = (GstHarnessCustomThread *) t;
+  ct->callback (ct, ct->data);
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (statechange, {})
+{
+  GstClock *clock = gst_element_get_clock (t->h->element);
+  GstIterator *it;
+  gboolean done = FALSE;
+
+  g_assert (gst_element_set_state (t->h->element, GST_STATE_NULL) ==
+      GST_STATE_CHANGE_SUCCESS);
+  g_thread_yield ();
+
+  it = gst_element_iterate_sink_pads (t->h->element);
+  while (!done) {
+    GValue item = G_VALUE_INIT;
+    switch (gst_iterator_next (it, &item)) {
+      case GST_ITERATOR_OK:
+      {
+        GstPad *sinkpad = g_value_get_object (&item);
+        GstPad *srcpad = gst_pad_get_peer (sinkpad);
+        if (srcpad != NULL) {
+          gst_pad_unlink (srcpad, sinkpad);
+          gst_pad_link (srcpad, sinkpad);
+          gst_object_unref (srcpad);
+        }
+        g_value_reset (&item);
+        break;
+      }
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (it);
+        break;
+      case GST_ITERATOR_ERROR:
+        g_assert_not_reached ();
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+    }
+    g_value_unset (&item);
+  }
+  gst_iterator_free (it);
+
+  if (clock != NULL) {
+    gst_element_set_clock (t->h->element, clock);
+    gst_object_unref (clock);
+  }
+  g_assert (gst_element_set_state (t->h->element, GST_STATE_PLAYING) ==
+      GST_STATE_CHANGE_SUCCESS);
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (buffer, {
+    GstHarnessPushBufferThread *pt = (GstHarnessPushBufferThread *) t;
+    gchar *sid;
+    /* Push stream start, caps and segment events */
+    sid = g_strdup_printf ("%s-%p", GST_OBJECT_NAME (t->h->element), t->h);
+    g_assert (gst_pad_push_event (t->h->srcpad,
+        gst_event_new_stream_start (sid))); g_free (sid);
+    g_assert (gst_pad_push_event (t->h->srcpad,
+        gst_event_new_caps (pt->caps)));
+    g_assert (gst_pad_push_event (t->h->srcpad,
+        gst_event_new_segment (&pt->segment)));
+  }
+)
+{
+  GstHarnessPushBufferThread *pt = (GstHarnessPushBufferThread *) t;
+  gst_harness_push (t->h, pt->func (t->h, pt->data));
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (event, {})
+{
+  GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t;
+  gst_harness_push_event (t->h, gst_event_ref (pet->event));
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (upstream_event, {})
+{
+  GstHarnessPushEventThread *pet = (GstHarnessPushEventThread *) t;
+  gst_harness_push_upstream_event (t->h, gst_event_ref (pet->event));
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (property, {})
+{
+  GstHarnessPropThread *pt = (GstHarnessPropThread *) t;
+  GValue value = G_VALUE_INIT;
+
+  g_object_set_property (G_OBJECT (t->h->element), pt->name, &pt->value);
+
+  g_value_init (&value, G_VALUE_TYPE (&pt->value));
+  g_object_get_property (G_OBJECT (t->h->element), pt->name, &value);
+  g_value_reset (&value);
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+GST_HARNESS_STRESS_FUNC_BEGIN (requestpad, {})
+{
+  GstHarnessReqPadThread *rpt = (GstHarnessReqPadThread *) t;
+  GstPad *reqpad;
+
+  if (rpt->release)
+    gst_harness_requestpad_release_pads (rpt);
+  g_thread_yield ();
+
+  reqpad = gst_element_request_pad (t->h->element,
+      rpt->templ, rpt->name, rpt->caps);
+  g_assert (reqpad != NULL);
+
+  rpt->pads = g_slist_prepend (rpt->pads, reqpad);
+}
+GST_HARNESS_STRESS_FUNC_END ()
+
+/**
+ * gst_harness_stress_thread_stop:
+ * @t: a #GstHarnessThread
+ *
+ * Stop the running #GstHarnessThread
+ *
+ * MT safe.
+ *
+ * Since: 1.6
+ */
+guint
+gst_harness_stress_thread_stop (GstHarnessThread * t)
+{
+  guint ret;
+
+  g_return_val_if_fail (t != NULL, 0);
+
+  ret = GST_HARNESS_THREAD_END (t);
+  g_ptr_array_remove (t->h->priv->stress, t);
+  return ret;
+}
+
+/**
+ * gst_harness_stress_custom_start: (skip)
+ * @h: a #GstHarness
+ * @init: a #GFunc that is called initially and only once
+ * @callback: a #GFunc that is called as often as possible
+ * @data: a #gpointer with custom data to pass to the @callback function
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each call to the @callback
+ *
+ * Start a custom stress-thread that will call your @callback for every
+ * iteration allowing you to do something nasty.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_custom_start (GstHarness * h,
+    GFunc init, GFunc callback, gpointer data, gulong sleep)
+{
+  GstHarnessCustomThread *t = g_slice_new0 (GstHarnessCustomThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_custom_thread_free, h, sleep);
+
+  t->init = init;
+  t->callback = callback;
+  t->data = data;
+
+  GST_HARNESS_THREAD_START (custom, t);
+  return &t->t;
+}
+
+/**
+ * gst_harness_stress_statechange_start_full: (skip)
+ * @h: a #GstHarness
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each state-change
+ *
+ * Change the state of your harnessed #GstElement from NULL to PLAYING and
+ * back again, only pausing for @sleep microseconds every time.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_statechange_start_full (GstHarness * h, gulong sleep)
+{
+  GstHarnessThread *t = g_slice_new0 (GstHarnessThread);
+  gst_harness_thread_init (t,
+      (GDestroyNotify) gst_harness_thread_free, h, sleep);
+  GST_HARNESS_THREAD_START (statechange, t);
+  return t;
+}
+
+static GstBuffer *
+gst_harness_ref_buffer (GstHarness * h, gpointer data)
+{
+  (void) h;
+  return gst_buffer_ref (GST_BUFFER_CAST (data));
+}
+
+/**
+ * gst_harness_stress_push_buffer_start_full: (skip)
+ * @h: a #GstHarness
+ * @caps: a #GstCaps for the #GstBuffer
+ * @segment: a #GstSegment
+ * @buf: a #GstBuffer to push
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each call to gst_pad_push
+ *
+ * Push a #GstBuffer in intervals of @sleep microseconds.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_push_buffer_start_full (GstHarness * h,
+    GstCaps * caps, const GstSegment * segment, GstBuffer * buf, gulong sleep)
+{
+  return gst_harness_stress_push_buffer_with_cb_start_full (h, caps, segment,
+      gst_harness_ref_buffer, gst_buffer_ref (buf),
+      (GDestroyNotify) gst_buffer_unref, sleep);
+}
+
+/**
+ * gst_harness_stress_push_buffer_with_cb_start_full: (skip)
+ * @h: a #GstHarness
+ * @caps: a #GstCaps for the #GstBuffer
+ * @segment: a #GstSegment
+ * @func: a #GstHarnessPrepareBuffer function called before every iteration
+ * to prepare / create a #GstBuffer for pushing
+ * @data: a #gpointer with data to the #GstHarnessPrepareBuffer function
+ * @notify: a #GDestroyNotify that is called for every push to allow cleaning
+ * up the #GstBuffer. (like gst_buffer_unref)
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each call to gst_pad_push
+ *
+ * Push a #GstBuffer in intervals of @sleep microseconds.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_push_buffer_with_cb_start_full (GstHarness * h,
+    GstCaps * caps, const GstSegment * segment,
+    GstHarnessPrepareBuffer func, gpointer data, GDestroyNotify notify,
+    gulong sleep)
+{
+  GstHarnessPushBufferThread *t = g_slice_new0 (GstHarnessPushBufferThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_push_buffer_thread_free, h, sleep);
+
+  gst_caps_replace (&t->caps, caps);
+  t->segment = *segment;
+  t->func = func;
+  t->data = data;
+  t->notify = notify;
+
+  GST_HARNESS_THREAD_START (buffer, t);
+  return &t->t;
+}
+
+/**
+ * gst_harness_stress_push_event_start_full: (skip)
+ * @h: a #GstHarness
+ * @event: a #GstEvent to push
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each gst_event_push with @event
+ *
+ * Push the @event onto the harnessed #GstElement sinkpad in intervals of
+ * @sleep microseconds
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_push_event_start_full (GstHarness * h,
+    GstEvent * event, gulong sleep)
+{
+  GstHarnessPushEventThread *t = g_slice_new0 (GstHarnessPushEventThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_push_event_thread_free, h, sleep);
+
+  t->event = gst_event_ref (event);
+  GST_HARNESS_THREAD_START (event, t);
+  return &t->t;
+}
+
+/**
+ * gst_harness_stress_push_upstream_event_start_full: (skip)
+ * @h: a #GstHarness
+ * @event: a #GstEvent to push
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each gst_event_push with @event
+ *
+ * Push the @event onto the harnessed #GstElement srcpad in intervals of
+ * @sleep microseconds.
+ * Pushing events should generally be OOB events.
+ * If you need serialized events, you may use a custom stress thread which
+ * both pushes buffers and events.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_push_upstream_event_start_full (GstHarness * h,
+    GstEvent * event, gulong sleep)
+{
+  GstHarnessPushEventThread *t = g_slice_new0 (GstHarnessPushEventThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_push_event_thread_free, h, sleep);
+
+  t->event = gst_event_ref (event);
+  GST_HARNESS_THREAD_START (upstream_event, t);
+  return &t->t;
+}
+
+/**
+ * gst_harness_stress_property_start_full: (skip)
+ * @h: a #GstHarness
+ * @name: a #gchar specifying a property name
+ * @value: a #GValue to set the property to
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each g_object_set with @name and @value
+ *
+ * Call g_object_set with @name and @value in intervals of @sleep microseconds
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_property_start_full (GstHarness * h,
+    const gchar * name, const GValue * value, gulong sleep)
+{
+  GstHarnessPropThread *t = g_slice_new0 (GstHarnessPropThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_property_thread_free, h, sleep);
+
+  t->name = g_strdup (name);
+  g_value_init (&t->value, G_VALUE_TYPE (value));
+  g_value_copy (value, &t->value);
+
+  GST_HARNESS_THREAD_START (property, t);
+  return &t->t;
+}
+
+/**
+ * gst_harness_stress_requestpad_start_full: (skip)
+ * @h: a #GstHarness
+ * @templ: a #GstPadTemplate
+ * @name: a #gchar
+ * @caps: a #GstCaps
+ * @release: a #gboolean
+ * @sleep: a #gulong specifying how long to sleep in (microseconds) for
+ * each gst_element_request_pad
+ *
+ * Call gst_element_request_pad in intervals of @sleep microseconds
+ *
+ * MT safe.
+ *
+ * Returns: a #GstHarnessThread
+ *
+ * Since: 1.6
+ */
+GstHarnessThread *
+gst_harness_stress_requestpad_start_full (GstHarness * h,
+    GstPadTemplate * templ, const gchar * name, GstCaps * caps,
+    gboolean release, gulong sleep)
+{
+  GstHarnessReqPadThread *t = g_slice_new0 (GstHarnessReqPadThread);
+  gst_harness_thread_init (&t->t,
+      (GDestroyNotify) gst_harness_requestpad_thread_free, h, sleep);
+
+  t->templ = gst_object_ref (templ);
+  t->name = g_strdup (name);
+  gst_caps_replace (&t->caps, caps);
+  t->release = release;
+
+  GST_HARNESS_THREAD_START (requestpad, t);
+  return &t->t;
+}
diff --git a/libs/gst/check/gstharness.h b/libs/gst/check/gstharness.h
new file mode 100644 (file)
index 0000000..f56adba
--- /dev/null
@@ -0,0 +1,197 @@
+/* GstHarness - A test-harness for GStreamer testing
+ *
+ * Copyright (C) 2012-2015 Pexip <pexip.com>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_HARNESS_H__
+#define __GST_HARNESS_H__
+
+#include <gst/gst.h>
+#include <gst/check/gsttestclock.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstHarness GstHarness;
+typedef struct _GstHarnessPrivate GstHarnessPrivate;
+typedef struct _GstHarnessThread GstHarnessThread;
+
+struct _GstHarness {
+  GstElement * element;
+
+  GstPad * srcpad;
+  GstPad * sinkpad;
+
+  GstHarness * src_harness;
+  GstHarness * sink_harness;
+
+  GstHarnessPrivate * priv;
+};
+
+/* Harness creation */
+GstHarness * gst_harness_new_full (GstElement * element,
+    GstStaticPadTemplate * hsrc, const gchar * sinkpad,
+    GstStaticPadTemplate * hsink, const gchar * srcpad);
+GstHarness * gst_harness_new_with_element (GstElement * element,
+    const gchar * sinkpad, const gchar * srcpad);
+GstHarness * gst_harness_new_with_padnames (const gchar * element_name,
+    const gchar * sinkpad, const gchar * srcpad);
+GstHarness * gst_harness_new_with_templates (const gchar * element_name,
+    GstStaticPadTemplate * hsrc, GstStaticPadTemplate * hsink);
+GstHarness * gst_harness_new (const gchar * element_name);
+GstHarness * gst_harness_new_parse (const gchar * launchline);
+void gst_harness_teardown (GstHarness * h);
+
+void gst_harness_add_element_src_pad (GstHarness * h, GstPad * srcpad);
+void gst_harness_add_element_sink_pad (GstHarness * h, GstPad * sinkpad);
+
+/* Caps Functions */
+void gst_harness_set_src_caps (GstHarness * h, GstCaps * caps);
+void gst_harness_set_sink_caps (GstHarness * h, GstCaps * caps);
+void gst_harness_set_caps (GstHarness * h, GstCaps * in, GstCaps * out);
+void gst_harness_set_src_caps_str (GstHarness * h, const gchar * str);
+void gst_harness_set_sink_caps_str (GstHarness * h, const gchar * str);
+void gst_harness_set_caps_str (GstHarness * h,
+    const gchar * in, const gchar * out);
+
+/* Clock Functions */
+void gst_harness_use_systemclock (GstHarness * h);
+void gst_harness_use_testclock (GstHarness * h);
+GstTestClock * gst_harness_get_testclock (GstHarness * h);
+gboolean gst_harness_set_time (GstHarness * h, GstClockTime time);
+gboolean gst_harness_wait_for_clock_id_waits (GstHarness * h,
+    guint waits, guint timeout);
+gboolean gst_harness_crank_single_clock_wait (GstHarness * h);
+gboolean gst_harness_crank_multiple_clock_waits (GstHarness * h,
+    guint waits);
+
+void gst_harness_play (GstHarness * h);
+void gst_harness_set_blocking_push_mode (GstHarness * h);
+
+/* buffers */
+GstBuffer * gst_harness_create_buffer (GstHarness * h, gsize size);
+GstFlowReturn gst_harness_push (GstHarness * h, GstBuffer * buffer);
+GstBuffer * gst_harness_pull (GstHarness * h);
+GstBuffer * gst_harness_try_pull (GstHarness * h);
+GstBuffer * gst_harness_push_and_pull (GstHarness * h, GstBuffer * buffer);
+guint gst_harness_buffers_received (GstHarness * h);
+guint gst_harness_buffers_in_queue (GstHarness * h);
+void gst_harness_set_drop_buffers (GstHarness * h, gboolean drop_buffers);
+void gst_harness_dump_to_file (GstHarness * h, const gchar * filename);
+GstClockTime gst_harness_get_last_pushed_timestamp (GstHarness * h);
+
+/* downstream events */
+gboolean gst_harness_push_event (GstHarness * h, GstEvent * event);
+GstEvent * gst_harness_pull_event (GstHarness * h);
+GstEvent * gst_harness_try_pull_event (GstHarness * h);
+guint gst_harness_events_received (GstHarness * h);
+guint gst_harness_events_in_queue (GstHarness * h);
+
+/* upstream events */
+gboolean gst_harness_push_upstream_event (GstHarness * h, GstEvent * event);
+GstEvent * gst_harness_pull_upstream_event (GstHarness * h);
+GstEvent * gst_harness_try_pull_upstream_event (GstHarness * h);
+guint gst_harness_upstream_events_received (GstHarness * h);
+guint gst_harness_upstream_events_in_queue (GstHarness * h);
+
+/* latency */
+GstClockTime gst_harness_query_latency (GstHarness * h);
+void gst_harness_set_upstream_latency (GstHarness * h, GstClockTime latency);
+
+/* allocator and allocation params */
+void gst_harness_set_propose_allocator (GstHarness * h, GstAllocator * allocator,
+    const GstAllocationParams * params);
+void gst_harness_get_allocator (GstHarness * h, GstAllocator ** allocator,
+    GstAllocationParams * params);
+
+/* src-harness */
+void gst_harness_add_src (GstHarness * h,
+    const gchar * src_element_name, gboolean has_clock_wait);
+void gst_harness_add_src_parse (GstHarness * h,
+    const gchar * launchline, gboolean has_clock_wait);
+GstFlowReturn gst_harness_push_from_src (GstHarness * h);
+GstFlowReturn gst_harness_src_crank_and_push_many (GstHarness * h,
+    gint cranks, gint pushes);
+gboolean gst_harness_src_push_event (GstHarness * h);
+
+/* sink-harness */
+void gst_harness_add_sink (GstHarness * h,
+    const gchar * sink_element_name);
+void gst_harness_add_sink_parse (GstHarness * h,
+    const gchar * launchline);
+GstFlowReturn gst_harness_push_to_sink (GstHarness * h);
+GstFlowReturn gst_harness_sink_push_many (GstHarness * h, gint pushes);
+
+/* convenience functions */
+GstElement * gst_harness_find_element (GstHarness * h,
+    const gchar * element_name);
+void gst_harness_set (GstHarness * h,
+    const gchar * element_name, const gchar * first_property_name, ...);
+void gst_harness_get (GstHarness * h,
+    const gchar * element_name, const gchar * first_property_name, ...);
+void gst_harness_add_probe (GstHarness * h,
+    const gchar * element_name, const gchar * pad_name, GstPadProbeType mask,
+    GstPadProbeCallback callback, gpointer user_data,
+    GDestroyNotify destroy_data);
+
+/* Stress */
+guint gst_harness_stress_thread_stop (GstHarnessThread * t);
+GstHarnessThread * gst_harness_stress_custom_start (GstHarness * h,
+    GFunc init, GFunc callback, gpointer data, gulong sleep);
+
+#define gst_harness_stress_statechange_start(h)                                \
+  gst_harness_stress_statechange_start_full (h, G_USEC_PER_SEC / 100)
+GstHarnessThread * gst_harness_stress_statechange_start_full (GstHarness * h,
+    gulong sleep);
+
+#define gst_harness_stress_push_buffer_start(h, c, s, b)                       \
+  gst_harness_stress_push_buffer_start_full (h, c, s, b, 0)
+GstHarnessThread * gst_harness_stress_push_buffer_start_full (GstHarness * h,
+    GstCaps * caps, const GstSegment * segment, GstBuffer * buf, gulong sleep);
+
+typedef GstBuffer * (*GstHarnessPrepareBuffer) (GstHarness * h, gpointer data);
+#define gst_harness_stress_push_buffer_with_cb_start(h, c, s, f, d, n)         \
+  gst_harness_stress_push_buffer_with_cb_start_full (h, c, s, f, d, n, 0)
+GstHarnessThread * gst_harness_stress_push_buffer_with_cb_start_full (
+    GstHarness * h, GstCaps * caps, const GstSegment * segment,
+    GstHarnessPrepareBuffer func, gpointer data, GDestroyNotify notify,
+    gulong sleep);
+
+#define gst_harness_stress_push_event_start(h, e)                              \
+  gst_harness_stress_push_event_start_full (h, e, 0)
+GstHarnessThread * gst_harness_stress_push_event_start_full (GstHarness * h,
+    GstEvent * event, gulong sleep);
+
+#define gst_harness_stress_send_upstream_event_start(h, e)                     \
+  gst_harness_stress_push_upstream_event_start_full (h, e, 0)
+GstHarnessThread * gst_harness_stress_push_upstream_event_start_full (
+    GstHarness * h, GstEvent * event, gulong sleep);
+
+#define gst_harness_stress_property_start(h, n, v)                             \
+  gst_harness_stress_property_start_full (h, n, v, G_USEC_PER_SEC / 1000)
+GstHarnessThread * gst_harness_stress_property_start_full (GstHarness * h,
+    const gchar * name, const GValue * value, gulong sleep);
+
+#define gst_harness_stress_requestpad_start(h, t, n, c, r)                     \
+  gst_harness_stress_requestpad_start_full (h, t, n, c, r, G_USEC_PER_SEC / 100)
+GstHarnessThread * gst_harness_stress_requestpad_start_full (GstHarness * h,
+    GstPadTemplate * templ, const gchar * name, GstCaps * caps,
+    gboolean release, gulong sleep);
+
+G_END_DECLS
+
+#endif /* __GST_HARNESS_H__ */