asset: fix handling of proxies
authorHenry Wilkes <hwilkes@igalia.com>
Thu, 5 Mar 2020 18:15:41 +0000 (18:15 +0000)
committerThibault Saunier <tsaunier@igalia.com>
Thu, 5 Mar 2020 20:04:51 +0000 (17:04 -0300)
Previous usage of the property proxy-target seemed to alternate between
the two definitions:
+ The asset we are the default proxy of
+ The asset we are in the proxy list of
Now, the latter definition is used, which seems more useful to a user
since knowing the latter can easily allow you to find out the former.

The previous behaviour of ges_asset_set_proxy (asset, NULL) was not very
clear. It is now defined so that it clears all the proxies for 'asset'.
This means that after this call, the GESAsset:proxy property will indeed
be NULL.

Also fixed:
+ We can call ges_asset_set_proxy (asset, proxy) when 'proxy' is already
  in the proxy list of 'asset'.
+ Handling of removing the default proxy in ges_asset_unproxy. This was
  sending out the wrong notifies.
+ Prohibiting circular proxying. Before we could only prevent one case,
  we should now be able to prevent all cases. This will prevent a hang
  in ges_asset_request.

ges/ges-asset.c
tests/check/ges/asset.c

index f60b02a7e33a6e200a2fbe36c30c8bc6e9f8a8a7..4625ea9196087a3539b9973c89293edd84a1f7c8 100644 (file)
@@ -167,6 +167,7 @@ struct _GESAssetPrivate
 
   /* actual list of proxies */
   GList *proxies;
+  /* the asset whose proxies list we belong to */
   GESAsset *proxy_target;
 
   /* The error that occurred when an asset has been initialized with error */
@@ -502,8 +503,8 @@ ges_asset_class_init (GESAssetClass * klass)
    * proxy will act as a substitute for the original asset when the
    * original is requested (see ges_asset_request()).
    *
-   * Setting this property will not remove the existing proxy, but will
-   * replace it as the default (see ges_asset_set_proxy()).
+   * Setting this property will not usually remove the existing proxy, but
+   * will replace it as the default (see ges_asset_set_proxy()).
    */
   _properties[PROP_PROXY] =
       g_param_spec_object ("proxy", "Proxy",
@@ -512,8 +513,18 @@ ges_asset_class_init (GESAssetClass * klass)
   /**
    * GESAsset:proxy-target:
    *
-   * The asset that this asset is a proxy of, or %NULL if it is not a
+   * The asset that this asset is a proxy for, or %NULL if it is not a
    * proxy for another asset.
+   *
+   * Note that even if this asset is acting as a proxy for another asset,
+   * but this asset is not the default #GESAsset:proxy, then @proxy-target
+   * will *still* point to this other asset. So you should check the
+   * #GESAsset:proxy property of @target-proxy before assuming it is the
+   * current default proxy for the target.
+   *
+   * Note that the #GObject::notify for this property is emitted after
+   * the #GESAsset:proxy #GObject::notify for the corresponding (if any)
+   * asset it is now the proxy of/no longer the proxy of.
    */
   _properties[PROP_PROXY_TARGET] =
       g_param_spec_object ("proxy-target", "Proxy target",
@@ -860,67 +871,89 @@ ges_asset_finish_proxy (GESAsset * proxy)
   return ges_asset_set_proxy (proxied_asset, proxy);
 }
 
+static gboolean
+_contained_in_proxy_tree (GESAsset * node, GESAsset * search)
+{
+  GList *tmp;
+  if (node == search)
+    return TRUE;
+  for (tmp = node->priv->proxies; tmp; tmp = tmp->next) {
+    if (_contained_in_proxy_tree (tmp->data, search))
+      return TRUE;
+  }
+  return FALSE;
+}
+
 /**
  * ges_asset_set_proxy:
  * @asset: The #GESAsset to proxy
  * @proxy: (allow-none): A new default proxy for @asset
  *
- * Sets one asset as the default #GESAsset:proxy of another (the
- * original asset).
+ * Sets the #GESAsset:proxy for the asset.
  *
- * If the given proxy is among the existing proxies of the asset (see
+ * If @proxy is among the existing proxies of the asset (see
  * ges_asset_list_proxies()) it will be moved to become the default
- * proxy. Otherwise, the proxy will be added to the list of proxies, as
- * the new default. The previous default proxy will become 'next in line'
- * for if the new one is removed, and so on. As such, this method will
- * **not** actually remove the previous default proxy
- * (use ges_asset_unproxy() for that).
+ * proxy. Otherwise, if @proxy is not %NULL, it will be added to the list
+ * of proxies, as the new default. The previous default proxy will become
+ * 'next in line' for if the new one is removed, and so on. As such, this
+ * will **not** actually remove the previous default proxy (use
+ * ges_asset_unproxy() for that).
  *
  * Note that an asset can only act as a proxy for one other asset.
  *
+ * As a special case, if @proxy is %NULL, then this method will actually
+ * remove **all** proxies from the asset.
+ *
  * Returns: %TRUE if @proxy was successfully set as the default for
  * @asset.
  */
 gboolean
 ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
 {
+  GESAsset *current_target;
   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
   g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
   g_return_val_if_fail (asset != proxy, FALSE);
 
   if (!proxy) {
+    GList *tmp, *proxies;
     if (asset->priv->error) {
       GST_ERROR_OBJECT (asset,
-          "Proxy was loaded with error (%s), it should not be 'unproxied'",
+          "Asset was loaded with error (%s), it should not be 'unproxied'",
           asset->priv->error->message);
 
       return FALSE;
     }
 
-    if (asset->priv->proxies) {
-      GESAsset *old_proxy = GES_ASSET (asset->priv->proxies->data);
+    GST_DEBUG_OBJECT (asset, "Removing all proxies");
+    proxies = asset->priv->proxies;
+    asset->priv->proxies = NULL;
 
-      old_proxy->priv->proxy_target = NULL;
-      g_object_notify_by_pspec (G_OBJECT (old_proxy),
-          _properties[PROP_PROXY_TARGET]);
+    for (tmp = proxies; tmp; tmp = tmp->next) {
+      GESAsset *proxy = tmp->data;
+      proxy->priv->proxy_target = NULL;
     }
-
-    GST_DEBUG_OBJECT (asset, "%s not proxied anymore", asset->priv->id);
     asset->priv->state = ASSET_INITIALIZED;
+
     g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+    for (tmp = proxies; tmp; tmp = tmp->next)
+      g_object_notify_by_pspec (G_OBJECT (tmp->data),
+          _properties[PROP_PROXY_TARGET]);
 
+    g_list_free (proxies);
     return TRUE;
   }
+  current_target = proxy->priv->proxy_target;
 
-  if (proxy->priv->proxy_target) {
+  if (current_target && current_target != asset) {
     GST_ERROR_OBJECT (asset,
-        "Trying to use %s as a proxy, but it is already proxying %s",
-        proxy->priv->id, proxy->priv->proxy_target->priv->id);
+        "Trying to use '%s' as a proxy, but it is already proxying '%s'",
+        proxy->priv->id, current_target->priv->id);
 
     return FALSE;
   }
 
-  if (g_list_find (proxy->priv->proxies, asset)) {
+  if (_contained_in_proxy_tree (proxy, asset)) {
     GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
 
     return FALSE;
@@ -929,17 +962,18 @@ ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
   if (g_list_find (asset->priv->proxies, proxy)) {
     GST_INFO_OBJECT (asset,
         "%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
-    GES_ASSET (asset->priv->proxies->data)->priv->proxy_target = NULL;
     asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
   }
 
   GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
   asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
-  proxy->priv->proxy_target = asset;
-  g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
 
+  proxy->priv->proxy_target = asset;
   asset->priv->state = ASSET_PROXIED;
+
   g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+  if (current_target != asset)
+    g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
 
   /* FIXME: ->inform_proxy is not called. We should figure out what the
    * purpose of ->inform_proxy should be generically. Currently, it is
@@ -965,21 +999,39 @@ ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
 gboolean
 ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
 {
+  gboolean removing_default;
+  gboolean last_proxy;
   g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
   g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
   g_return_val_if_fail (asset != proxy, FALSE);
 
+  /* also tests if the list is NULL */
   if (!g_list_find (asset->priv->proxies, proxy)) {
-    GST_INFO_OBJECT (asset, "%s is not a proxy.", proxy->priv->id);
+    GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id);
 
     return FALSE;
   }
 
-  if (asset->priv->proxies->data == proxy)
-    ges_asset_set_proxy (asset, NULL);
+  last_proxy = (asset->priv->proxies->next == NULL);
+  if (last_proxy && asset->priv->error) {
+    GST_ERROR_OBJECT (asset,
+        "Asset was loaded with error (%s), its last proxy '%s' should "
+        "not be removed", asset->priv->error->message, proxy->priv->id);
+    return FALSE;
+  }
+
+  removing_default = (asset->priv->proxies->data == proxy);
 
   asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
 
+  if (last_proxy)
+    asset->priv->state = ASSET_INITIALIZED;
+  proxy->priv->proxy_target = NULL;
+
+  if (removing_default)
+    g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
+  g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
+
   return TRUE;
 }
 
index 7cf6b19685442b000273ddcf54f1c9bbdba94392..3affc344548b629b97cdb1a89a97e4b7518d479b 100644 (file)
@@ -327,6 +327,413 @@ GST_START_TEST (test_proxy_asset)
 GST_END_TEST;
 */
 
+static void
+_count_cb (GObject * obj, GParamSpec * pspec, gpointer key)
+{
+  guint count = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (obj), key));
+  g_object_set_data (G_OBJECT (obj), key, GUINT_TO_POINTER (count + 1));
+}
+
+#define _CONNECT_PROXY_SIGNALS(asset) \
+  g_signal_connect (asset, "notify::proxy", G_CALLBACK (_count_cb), \
+      (gchar *)"test-data-proxy-count"); \
+  g_signal_connect (asset, "notify::proxy-target", G_CALLBACK (_count_cb), \
+      (gchar *)"test-data-target-count");
+
+/* test that @asset has the properties proxy = @proxy and
+ * proxy-target = @proxy_target
+ * Also check that the callback for "notify::proxy" (set up in
+ * _CONNECT_PROXY_SIGNALS) has been called @p_count times, and the
+ * callback for "notify::target-proxy" has been called @t_count times.
+ */
+#define _assert_proxy_state(asset, proxy, proxy_target, p_count, t_count) \
+{ \
+  const gchar *id = ges_asset_get_id (asset); \
+  guint found_p_count = GPOINTER_TO_UINT (g_object_get_data ( \
+        G_OBJECT (asset), "test-data-proxy-count")); \
+  guint found_t_count = GPOINTER_TO_UINT (g_object_get_data ( \
+        G_OBJECT (asset), "test-data-target-count")); \
+  GESAsset *found_proxy = ges_asset_get_proxy (asset); \
+  GESAsset *found_target = ges_asset_get_proxy_target (asset); \
+  fail_unless (found_proxy == proxy, "Asset '%s' has the proxy '%s' " \
+      "rather than the expected '%s'", id, \
+      found_proxy ? ges_asset_get_id (found_proxy) : NULL, \
+      proxy ? ges_asset_get_id (proxy) : NULL); \
+  fail_unless (found_target == proxy_target, "Asset '%s' has the proxy " \
+      "target '%s' rather than the expected '%s'", id, \
+      found_target ? ges_asset_get_id (found_target) : NULL, \
+      proxy_target ? ges_asset_get_id (proxy_target) : NULL); \
+  fail_unless (p_count == found_p_count, "notify::proxy for asset '%s' " \
+      "was called %u times, rather than the expected %u times", \
+      id, found_p_count, p_count); \
+  fail_unless (t_count == found_t_count, "notify::target-proxy for " \
+      "asset '%s' was called %u times, rather than the expected %u times", \
+      id, found_t_count, t_count); \
+}
+
+#define _assert_proxy_list(asset, cmp_list) \
+{ \
+  const gchar * id = ges_asset_get_id (asset); \
+  int i; \
+  GList *tmp; \
+  for (i = 0, tmp = ges_asset_list_proxies (asset); cmp_list[i] && tmp; \
+      i++, tmp = tmp->next) { \
+    GESAsset *proxy = tmp->data; \
+    fail_unless (proxy == cmp_list[i], "The asset '%s' has '%s' as its " \
+        "%ith proxy, rather than the expected '%s'", id, \
+        ges_asset_get_id (proxy), i, ges_asset_get_id (cmp_list[i])); \
+  } \
+  fail_unless (tmp == NULL, "Found more proxies for '%s' than expected", \
+      id); \
+  fail_unless (cmp_list[i] == NULL, "Found less proxies (%i) for '%s' " \
+      "than expected", i, id); \
+}
+
+#define _assert_effect_asset_request(req_id, expect) \
+{ \
+  GESAsset *requested = ges_asset_request (GES_TYPE_EFFECT, req_id, NULL); \
+  fail_unless (requested == expect, "Requested asset for id '%s' is " \
+      "'%s' rather than the expected '%s'", req_id, \
+      requested ? ges_asset_get_id (requested) : NULL, \
+      ges_asset_get_id (expect)); \
+  gst_object_unref (requested); \
+}
+
+GST_START_TEST (test_proxy_setters)
+{
+  GESAsset *proxies[] = { NULL, NULL, NULL, NULL };
+  GESAsset *asset, *alt_asset;
+  GESAsset *proxy0, *proxy1, *proxy2;
+  gchar asset_id[] = "video agingtv ! videobalance";
+  gchar alt_asset_id[] = "video gamma";
+  gchar proxy0_id[] = "video videobalance contrast=0.0";
+  gchar proxy1_id[] = "video videobalance contrast=1.0";
+  gchar proxy2_id[] = "video videobalance contrast=2.0";
+
+  ges_init ();
+
+  asset = ges_asset_request (GES_TYPE_EFFECT, asset_id, NULL);
+  alt_asset = ges_asset_request (GES_TYPE_EFFECT, alt_asset_id, NULL);
+
+  proxy0 = ges_asset_request (GES_TYPE_EFFECT, proxy0_id, NULL);
+  proxy1 = ges_asset_request (GES_TYPE_EFFECT, proxy1_id, NULL);
+  proxy2 = ges_asset_request (GES_TYPE_EFFECT, proxy2_id, NULL);
+
+  /* make sure our assets are unique */
+  fail_unless (asset);
+  fail_unless (alt_asset);
+  fail_unless (proxy0);
+  fail_unless (proxy1);
+  fail_unless (proxy2);
+  fail_unless (asset != alt_asset);
+  fail_unless (asset != proxy0);
+  fail_unless (asset != proxy1);
+  fail_unless (asset != proxy2);
+  fail_unless (alt_asset != proxy0);
+  fail_unless (alt_asset != proxy1);
+  fail_unless (alt_asset != proxy2);
+  fail_unless (proxy0 != proxy1);
+  fail_unless (proxy0 != proxy1);
+  fail_unless (proxy0 != proxy2);
+  fail_unless (proxy1 != proxy2);
+
+  _CONNECT_PROXY_SIGNALS (asset);
+  _CONNECT_PROXY_SIGNALS (alt_asset);
+  _CONNECT_PROXY_SIGNALS (proxy0);
+  _CONNECT_PROXY_SIGNALS (proxy1);
+  _CONNECT_PROXY_SIGNALS (proxy2);
+
+  /* no proxies to start with */
+  _assert_proxy_state (asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (alt_asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy0, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+  _assert_proxy_list (asset, proxies);
+  _assert_proxy_list (alt_asset, proxies);
+  _assert_proxy_list (proxy0, proxies);
+  _assert_proxy_list (proxy1, proxies);
+  _assert_proxy_list (proxy2, proxies);
+
+  /* id for an asset with no proxy returns itself */
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (alt_asset_id, alt_asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* set a proxy */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  _assert_proxy_state (asset, proxy0, NULL, 1, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  proxies[0] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  /* requesting the same asset should return the proxy instead */
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* can't proxy a different asset */
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (alt_asset, proxy0) == FALSE);
+  _assert_proxy_state (alt_asset, NULL, NULL, 0, 0);
+  _assert_proxy_state (asset, proxy0, NULL, 1, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  _assert_proxy_list (asset, proxies);
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+
+  /* set the same proxy again is safe */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  /* notify::proxy callback count increases, even though we set the same
+   * proxy. This is the default behaviour for setters. */
+  _assert_proxy_state (asset, proxy0, NULL, 2, 0);
+  /* but the notify::target-proxy has not increased for the proxy */
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, NULL, 0, 0);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  _assert_proxy_list (asset, proxies);
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+
+  /* replace the proxy with a new one */
+  fail_unless (ges_asset_set_proxy (asset, proxy1));
+  _assert_proxy_state (asset, proxy1, NULL, 3, 0);
+  /* first proxy still keeps its target */
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 0);
+
+  proxies[0] = proxy1;
+  proxies[1] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* replace again */
+  fail_unless (ges_asset_set_proxy (asset, proxy2));
+  _assert_proxy_state (asset, proxy2, NULL, 4, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, asset, 0, 1);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy1;
+  proxies[2] = proxy0;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* move proxy0 back to being the default */
+  fail_unless (ges_asset_set_proxy (asset, proxy0));
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, asset, 0, 1);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy2;
+  proxies[2] = proxy1;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy2 */
+  fail_unless (ges_asset_unproxy (asset, proxy2));
+  /* notify::proxy not released since we have not switched defaults */
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 2);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy1;
+  proxies[2] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy0);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* make proxy2 a proxy for proxy0 */
+  fail_unless (ges_asset_set_proxy (proxy0, proxy2));
+  _assert_proxy_state (asset, proxy0, NULL, 5, 0);
+  _assert_proxy_state (proxy0, proxy2, asset, 1, 1);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, proxy0, 0, 3);
+
+  proxies[0] = proxy0;
+  proxies[1] = proxy1;
+  proxies[2] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  /* original id will now follows two proxy links to get proxy2 */
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy2);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy0 from asset, should now default to proxy1 */
+  fail_unless (ges_asset_unproxy (asset, proxy0));
+  /* notify::proxy released since we have switched defaults */
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, proxy2, NULL, 1, 2);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, proxy0, 0, 3);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy2);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* remove proxy2 from proxy0 */
+  fail_unless (ges_asset_unproxy (proxy0, proxy2));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, NULL, 2, 2);
+  _assert_proxy_state (proxy1, NULL, asset, 0, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 4);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (proxy0, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy1);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* make both proxy0 and proxy2 proxies of proxy1 */
+  fail_unless (ges_asset_set_proxy (proxy1, proxy0));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy0, asset, 1, 1);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 4);
+  fail_unless (ges_asset_set_proxy (proxy1, proxy2));
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  proxies[0] = proxy1;
+  proxies[1] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy0;
+  proxies[2] = NULL;
+  _assert_proxy_list (proxy1, proxies);
+
+  _assert_effect_asset_request (asset_id, proxy2);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy2);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* should not be able to set up any circular proxies */
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy1, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy0, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+  /* Raises ERROR */
+  fail_unless (ges_asset_set_proxy (proxy2, asset) == FALSE);
+  _assert_proxy_state (asset, proxy1, NULL, 6, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, asset, 2, 1);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  /* remove last proxy from asset, should set its proxy to NULL */
+  fail_unless (ges_asset_unproxy (asset, proxy1));
+  _assert_proxy_state (asset, NULL, NULL, 7, 0);
+  _assert_proxy_state (proxy0, NULL, proxy1, 2, 3);
+  _assert_proxy_state (proxy1, proxy2, NULL, 2, 2);
+  _assert_proxy_state (proxy2, NULL, proxy1, 0, 5);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (asset, proxies);
+
+  proxies[0] = proxy2;
+  proxies[1] = proxy0;
+  proxies[2] = NULL;
+  _assert_proxy_list (proxy1, proxies);
+
+  /* get asset back */
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy2);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  /* set the proxy property to NULL for proxy1, should remove all of
+   * its proxies */
+  fail_unless (ges_asset_set_proxy (proxy1, NULL));
+  _assert_proxy_state (asset, NULL, NULL, 7, 0);
+  /* only one notify for proxy1, but two separate ones for ex-proxies */
+  _assert_proxy_state (proxy0, NULL, NULL, 2, 4);
+  _assert_proxy_state (proxy1, NULL, NULL, 3, 2);
+  _assert_proxy_state (proxy2, NULL, NULL, 0, 6);
+
+  proxies[0] = NULL;
+  _assert_proxy_list (asset, proxies);
+  _assert_proxy_list (proxy0, proxies);
+  _assert_proxy_list (proxy1, proxies);
+  _assert_proxy_list (proxy2, proxies);
+
+  _assert_effect_asset_request (asset_id, asset);
+  _assert_effect_asset_request (proxy0_id, proxy0);
+  _assert_effect_asset_request (proxy1_id, proxy1);
+  _assert_effect_asset_request (proxy2_id, proxy2);
+
+  gst_object_unref (asset);
+  gst_object_unref (alt_asset);
+  gst_object_unref (proxy0);
+  gst_object_unref (proxy1);
+  gst_object_unref (proxy2);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
+
 static Suite *
 ges_suite (void)
 {
@@ -340,6 +747,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_transition_change_asset);
   tcase_add_test (tc_chain, test_uri_clip_change_asset);
   tcase_add_test (tc_chain, test_list_asset);
+  tcase_add_test (tc_chain, test_proxy_setters);
 
   return s;
 }