text: /* Regression test for object registration and unregistration
   *
   * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
   * Copyright © 2009-2011 Nokia Corporation
   *
   * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later OR MIT
   *
   * In preparation for dbus-glib relicensing (if it ever happens), this file is
   * licensed under (at your option) either the AFL v2.1, the GPL v2 or later,
   * or an MIT/X11-style license:
   *
   * Licensed under the Academic Free License version 2.1
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * Permission is hereby granted, free of charge, to any person
   * obtaining a copy of this software and associated documentation
   * files (the "Software"), to deal in the Software without
   * restriction, including without limitation the rights to use, copy,
   * modify, merge, publish, distribute, sublicense, and/or sell copies
   * of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   */
  
  #include <config.h>
  #undef G_DISABLE_ASSERT
  
  #include <dbus/dbus-glib.h>
  #include <dbus/dbus-glib-lowlevel.h>
  
  #include "my-object.h"
  
  #include "dbus-gmain/tests/util.h"
  
  GMainLoop *loop = NULL;
  
  typedef struct {
      DBusError dbus_error;
      DBusGConnection *bus;
      DBusGConnection *bus2;
      GObject *object;
      DBusMessage *frobnicate1_message;
      DBusMessage *frobnicate2_message;
      gboolean received_objectified;
  } Fixture;
  
  #define assert_no_error(e) _assert_no_error (e, __FILE__, __LINE__)
  static void
  _assert_no_error (const DBusError *e,
      const char *file,
      int line)
  {
    if (G_UNLIKELY (dbus_error_is_set (e)))
      g_error ("%s:%d: expected success but got error: %s: %s",
          file, line, e->name, e->message);
  }
  
  static void
  setup (Fixture *f,
      gconstpointer path_to_use)
  {
    dbus_error_init (&f->dbus_error);
  
    f->bus = dbus_g_bus_get_private (DBUS_BUS_SESSION, NULL, NULL);
    g_assert_nonnull (f->bus);
  
    f->bus2 = dbus_g_bus_get_private (DBUS_BUS_SESSION, NULL, NULL);
    g_assert_nonnull (f->bus2);
  
    f->object = g_object_new (MY_TYPE_OBJECT, NULL);
    g_assert_true (MY_IS_OBJECT (f->object));
  }
  
  static void
  teardown (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    /* we close the connection before releasing the object, to test fd.o #5688
     * in test_lookup() */
    if (f->bus != NULL)
      {
        test_run_until_disconnected (dbus_g_connection_get_connection (f->bus), NULL);
        dbus_g_connection_unref (f->bus);
      }
  
    if (f->bus2 != NULL)
      {
        test_run_until_disconnected (dbus_g_connection_get_connection (f->bus2), NULL);
        dbus_g_connection_unref (f->bus2);
      }
  
    if (f->object != NULL)
      {
        g_object_unref (f->object);
      }
  
    /* This is safe to call on an initialized-but-unset DBusError, a bit like
     * g_clear_error */
    dbus_error_free (&f->dbus_error);
  
    dbus_shutdown ();
  }
  
  static void
  test_lookup (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    /* teardown() closes the connection before f->object is destroyed, which
     * used to be broken */
    g_test_bug ("5688");
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
    /* this was briefly broken while fixing fd.o#5688 */
    g_assert_null (dbus_g_connection_lookup_g_object (f->bus, "/bar"));
  }
  
  static void
  test_unregister (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    /* feature test: objects can be unregistered */
    g_test_bug ("21219");
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
    dbus_g_connection_unregister_g_object (f->bus, f->object);
    g_assert_null (dbus_g_connection_lookup_g_object (f->bus, "/foo"));
  }
  
  static void
  test_unregister_on_last_unref (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    gpointer weak_pointer;
  
    weak_pointer = f->object;
    g_object_add_weak_pointer (weak_pointer, &weak_pointer);
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
    /* implicit unregistration by the last-unref of the object */
    g_object_unref (f->object);
    f->object = NULL;
  
    g_assert_null (weak_pointer);
  
    g_assert_null (dbus_g_connection_lookup_g_object (f->bus, "/foo"));
  }
  
  static void
  test_unregister_on_forced_dispose (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
    /* implicit unregistration by dispose() of the object (don't try
     * this at home) */
    g_object_run_dispose (f->object);
  
    g_assert_null (dbus_g_connection_lookup_g_object (f->bus, "/foo"));
  }
  
  static void
  test_reregister (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
  
    /* Before 0.82, re-registering the same object path was leaky but successful.
     * 0.82 disallowed this behaviour. Since 0.84 it was meant to be allowed
     * again, and a no-op, but it actually had the effect of removing all
     * record of the registrations (while leaving the object registered with
     * libdbus). */
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
  
    /* This would critical in 0.84. */
    dbus_g_connection_unregister_g_object (f->bus, f->object);
    g_assert_null (dbus_g_connection_lookup_g_object (f->bus, "/foo"));
  }
  
  static DBusHandlerResult
  frobnicate_cb (DBusConnection *conn,
      DBusMessage *message,
      void *user_data)
  {
    Fixture *f = user_data;
  
    if (dbus_message_is_signal (message,
          "org.freedesktop.DBus.GLib.Tests.MyObject", "Frobnicate"))
      {
        const char *sender = dbus_message_get_sender (message);
        const char *path = dbus_message_get_path (message);
  
        g_assert_nonnull (sender);
        g_assert_nonnull (path);
  
        if (g_strcmp0 (path, "/foo") == 0)
          {
            g_assert_cmpstr (sender, ==, dbus_bus_get_unique_name (
                  dbus_g_connection_get_connection (f->bus)));
  
            g_assert_null (f->frobnicate1_message);
            f->frobnicate1_message = dbus_message_ref (message);
          }
        else
          {
            g_assert_cmpstr (path, ==, "/bar");
            g_assert_cmpstr (sender, ==, dbus_bus_get_unique_name (
                  dbus_g_connection_get_connection (f->bus2)));
  
            g_assert_null (f->frobnicate2_message);
            f->frobnicate2_message = dbus_message_ref (message);
          }
      }
  
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
  
  static void
  test_twice (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    dbus_bool_t mem;
  
    g_test_bug ("32087");
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
  
    dbus_g_connection_register_g_object (f->bus2, "/bar", f->object);
  
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus2, "/bar") ==
        f->object);
  
    dbus_bus_add_match (dbus_g_connection_get_connection (f->bus),
        "type='signal'", &f->dbus_error);
    assert_no_error (&f->dbus_error);
    mem = dbus_connection_add_filter (dbus_g_connection_get_connection (f->bus),
        frobnicate_cb, f, NULL);
    g_assert_true (mem);
  
    my_object_emit_frobnicate ((MyObject *) f->object, NULL);
  
    /* The object should emit the signal once onto each connection,
     * from the appropriate location */
    while (f->frobnicate1_message == NULL || f->frobnicate2_message == NULL)
      g_main_context_iteration (NULL, TRUE);
  
    dbus_message_unref (f->frobnicate1_message);
    f->frobnicate1_message = NULL;
    dbus_message_unref (f->frobnicate2_message);
    f->frobnicate2_message = NULL;
  
    /* try again, to catch any extra emissions, but first unregister one of the
     * object's locations */
    dbus_g_connection_unregister_g_object (f->bus, f->object);
    my_object_emit_frobnicate ((MyObject *) f->object, NULL);
  
    while (f->frobnicate2_message == NULL)
      g_main_context_iteration (NULL, TRUE);
  
    g_assert_null (f->frobnicate1_message);
    dbus_message_unref (f->frobnicate2_message);
    f->frobnicate2_message = NULL;
  }
  
  static void
  test_clean_slate (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    dbus_bool_t mem;
  
    dbus_bus_add_match (dbus_g_connection_get_connection (f->bus),
        "type='signal'", &f->dbus_error);
    assert_no_error (&f->dbus_error);
    mem = dbus_connection_add_filter (dbus_g_connection_get_connection (f->bus),
        frobnicate_cb, f, NULL);
    g_assert_true (mem);
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
  
    my_object_emit_frobnicate ((MyObject *) f->object, NULL);
  
    while (f->frobnicate1_message == NULL)
      g_main_context_iteration (NULL, TRUE);
  
    dbus_message_unref (f->frobnicate1_message);
    f->frobnicate1_message = NULL;
  
    /* unregister the object from its last object path, then put it back
     * in the same location */
    dbus_g_connection_unregister_g_object (f->bus, f->object);
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
  
    /* bug: in 0.92, this would be emitted twice because the hook was added
     * twice */
    my_object_emit_frobnicate ((MyObject *) f->object, NULL);
  
    while (f->frobnicate1_message == NULL)
      g_main_context_iteration (NULL, TRUE);
  
    dbus_message_unref (f->frobnicate1_message);
    f->frobnicate1_message = NULL;
  
    /* unregister the object from its last object path, then put it back
     * at a different location */
    dbus_g_connection_unregister_g_object (f->bus, f->object);
    dbus_g_connection_register_g_object (f->bus2, "/bar", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus2, "/bar") ==
        f->object);
  
    my_object_emit_frobnicate ((MyObject *) f->object, NULL);
  
    while (f->frobnicate2_message == NULL)
      g_main_context_iteration (NULL, TRUE);
  
    /* check that this wasn't received anyway, which would indicate that
     * either unregistration from /foo was unsuccessful, or the double
     * emission mentioned above was seen */
    g_assert_null (f->frobnicate1_message);
  
    dbus_message_unref (f->frobnicate2_message);
    f->frobnicate2_message = NULL;
  }
  
  static DBusHandlerResult
  objectified_cb (DBusConnection *conn,
      DBusMessage *message,
      void *user_data)
  {
    Fixture *f = user_data;
  
    if (dbus_message_is_signal (message,
          "org.freedesktop.DBus.GLib.Tests.MyObject", "Objectified"))
      {
        const char *sender = dbus_message_get_sender (message);
        const char *path = dbus_message_get_path (message);
        dbus_bool_t ok;
        DBusError e;
  
        dbus_error_init (&e);
  
        g_assert_nonnull (sender);
        g_assert_nonnull (path);
  
        g_assert_cmpstr (path, ==, "/foo");
        g_assert_cmpstr (sender, ==, dbus_bus_get_unique_name (
              dbus_g_connection_get_connection (f->bus)));
  
        path = NULL;
        ok = dbus_message_get_args (message, &e,
            DBUS_TYPE_OBJECT_PATH, &path,
            DBUS_TYPE_INVALID);
  
        if (dbus_error_is_set (&e))
          g_error ("%s: %s", e.name, e.message);
  
        g_assert_true (ok);
        g_assert_cmpstr (path, ==, "/foo");
  
        f->received_objectified = TRUE;
      }
  
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
  
  static void
  test_marshal_object (Fixture *f,
      gconstpointer test_data G_GNUC_UNUSED)
  {
    dbus_bool_t mem;
  
    g_test_bug ("37852");
  
    dbus_g_connection_register_g_object (f->bus, "/foo", f->object);
    g_assert_true (dbus_g_connection_lookup_g_object (f->bus, "/foo") ==
        f->object);
  
    dbus_bus_add_match (dbus_g_connection_get_connection (f->bus),
        "type='signal'", &f->dbus_error);
    assert_no_error (&f->dbus_error);
    mem = dbus_connection_add_filter (dbus_g_connection_get_connection (f->bus),
        objectified_cb, f, NULL);
    g_assert_true (mem);
  
    my_object_emit_objectified ((MyObject *) f->object, f->object);
  
    while (!f->received_objectified)
      g_main_context_iteration (NULL, TRUE);
  }
  
  int
  main (int argc, char **argv)
  {
    g_type_init ();
    g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL);
    dbus_g_type_specialized_init ();
    g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id=");
    g_test_init (&argc, &argv, NULL);
  
    g_test_add ("/registrations/lookup", Fixture, NULL,
        setup, test_lookup, teardown);
    g_test_add ("/registrations/unregister", Fixture, NULL,
        setup, test_unregister, teardown);
    g_test_add ("/registrations/unregister-on-last-unref", Fixture, NULL,
        setup, test_unregister_on_last_unref, teardown);
    g_test_add ("/registrations/unregister-on-forced-dispose", Fixture, NULL,
        setup, test_unregister_on_forced_dispose, teardown);
    g_test_add ("/registrations/reregister", Fixture, NULL,
        setup, test_reregister, teardown);
    g_test_add ("/registrations/twice", Fixture, NULL,
        setup, test_twice, teardown);
    g_test_add ("/registrations/clean-slate", Fixture, NULL,
        setup, test_clean_slate, teardown);
    g_test_add ("/registrations/marshal-object", Fixture, NULL,
        setup, test_marshal_object, teardown);
  
    return g_test_run ();
  }
  