LCOV - code coverage report
Current view: top level - libglib-testing - signal-logger.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 14 136 10.3 %
Date: 2022-03-10 11:32:37 Functions: 3 13 23.1 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 3 76 3.9 %

           Branch data     Line data    Source code
       1                 :            : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
       2                 :            :  *
       3                 :            :  * Copyright © 2018 Endless Mobile, Inc.
       4                 :            :  *
       5                 :            :  * This library is free software; you can redistribute it and/or
       6                 :            :  * modify it under the terms of the GNU Lesser General Public
       7                 :            :  * License as published by the Free Software Foundation; either
       8                 :            :  * version 2.1 of the License, or (at your option) any later version.
       9                 :            :  *
      10                 :            :  * This library is distributed in the hope that it will be useful,
      11                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13                 :            :  * Lesser General Public License for more details.
      14                 :            :  *
      15                 :            :  * You should have received a copy of the GNU Lesser General Public
      16                 :            :  * License along with this library; if not, write to the Free Software
      17                 :            :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      18                 :            :  *
      19                 :            :  * Authors:
      20                 :            :  *  - Philip Withnall <withnall@endlessm.com>
      21                 :            :  */
      22                 :            : 
      23                 :            : #include "config.h"
      24                 :            : 
      25                 :            : #include <glib.h>
      26                 :            : #include <glib-object.h>
      27                 :            : #include <gobject/gvaluecollector.h>
      28                 :            : #include <libglib-testing/signal-logger.h>
      29                 :            : 
      30                 :            : 
      31                 :            : /**
      32                 :            :  * SECTION:signal-logger
      33                 :            :  * @short_description: GObject signal logging and checking
      34                 :            :  * @stability: Unstable
      35                 :            :  * @include: libglib-testing/signal-logger.h
      36                 :            :  *
      37                 :            :  * #GtSignalLogger is an object which allows logging of signals emitted from
      38                 :            :  * zero or more #GObjects, and later comparison of those signals against what
      39                 :            :  * was expected to be emitted.
      40                 :            :  *
      41                 :            :  * A single #GtSignalLogger instance can be used for multiple #GObjects, and can
      42                 :            :  * outlive the objects themselves. It can be connected to several different
      43                 :            :  * signals, emissions of which will all be added to the same queue (ordered by
      44                 :            :  * emission time).
      45                 :            :  *
      46                 :            :  * Testing of the emitted signals is performed by popping emissions off the
      47                 :            :  * queue and comparing them to what was expected. Macros are provided to assert
      48                 :            :  * that the next emission on the queue was for a specific signal — or callers
      49                 :            :  * may unconditionally pop the next emission and compare its properties
      50                 :            :  * themselves.
      51                 :            :  *
      52                 :            :  * By default, a #GtSignalLogger will not assert that its emission queue is
      53                 :            :  * empty on destruction: that is up to the caller, and it is highly recommended
      54                 :            :  * that gt_signal_logger_assert_no_emissions() is called before a signal logger
      55                 :            :  * is destroyed, or after a particular unit test is completed.
      56                 :            :  *
      57                 :            :  * Since: 0.1.0
      58                 :            :  */
      59                 :            : 
      60                 :            : /**
      61                 :            :  * GtSignalLogger:
      62                 :            :  *
      63                 :            :  * An object which allows signal emissions from zero or more #GObjects to be
      64                 :            :  * logged easily, without needing to write specific callback functions for any
      65                 :            :  * of them.
      66                 :            :  *
      67                 :            :  * Since: 0.1.0
      68                 :            :  */
      69                 :            : struct _GtSignalLogger
      70                 :            : {
      71                 :            :   /* Log of the signal emissions. Head emission was the first emitted. */
      72                 :            :   GPtrArray *log;  /* (element-type GtSignalLoggerEmission) (owned) */
      73                 :            :   /* Set of currently connected signal handler closures. */
      74                 :            :   GPtrArray *closures;  /* (element-type GtLoggedClosure) (owned) */
      75                 :            : };
      76                 :            : 
      77                 :            : /**
      78                 :            :  * GtLoggedClosure:
      79                 :            :  *
      80                 :            :  * A closure representing a connection from @logger to the given @signal_name
      81                 :            :  * on @obj.
      82                 :            :  *
      83                 :            :  * The closure will be kept alive until the @logger is destroyed, though it will
      84                 :            :  * be invalidated and disconnected earlier if @obj is finalised.
      85                 :            :  *
      86                 :            :  * Since: 0.1.0
      87                 :            :  */
      88                 :            : typedef struct
      89                 :            : {
      90                 :            :   GClosure closure;
      91                 :            : 
      92                 :            :   GtSignalLogger *logger;  /* (not owned) */
      93                 :            : 
      94                 :            :   /* Pointer to the object instance this closure is connected to; no ref is
      95                 :            :    * held, and the object may be finalised before the closure, so this should
      96                 :            :    * only be used as an opaque pointer; add a #GWeakRef if the object needs to
      97                 :            :    * be accessed in future. */
      98                 :            :   gpointer obj;  /* (not owned) */
      99                 :            :   /* A copy of `G_OBJECT_TYPE_NAME (obj)` for use when @obj may be invalid. */
     100                 :            :   gchar *obj_type_name;  /* (owned) */
     101                 :            : 
     102                 :            :   /* Name of the signal this closure is connected to, including detail
     103                 :            :    * (if applicable). */
     104                 :            :   gchar *signal_name;  /* (owned) */
     105                 :            :   gulong signal_id;  /* 0 when disconnected */
     106                 :            : } GtLoggedClosure;
     107                 :            : 
     108                 :            : /**
     109                 :            :  * GtSignalLoggerEmission:
     110                 :            :  *
     111                 :            :  * The details of a particular signal emission, including its parameter values.
     112                 :            :  *
     113                 :            :  * @param_values does not include the object instance.
     114                 :            :  *
     115                 :            :  * Since: 0.1.0
     116                 :            :  */
     117                 :            : struct _GtSignalLoggerEmission
     118                 :            : {
     119                 :            :   /* The closure this emission was captured by. */
     120                 :            :   GtLoggedClosure *closure;  /* (owned) */
     121                 :            :   /* Array of parameter values, not including the object instance. */
     122                 :            :   GValue *param_values;  /* (array length=n_param_values) */
     123                 :            :   gsize n_param_values;
     124                 :            : };
     125                 :            : 
     126                 :            : /**
     127                 :            :  * gt_signal_logger_emission_free:
     128                 :            :  * @emission: (transfer full): a #GtSignalLoggerEmission
     129                 :            :  *
     130                 :            :  * Free a #GtSignalLoggerEmission.
     131                 :            :  *
     132                 :            :  * Since: 0.1.0
     133                 :            :  */
     134                 :            : void
     135                 :          0 : gt_signal_logger_emission_free (GtSignalLoggerEmission *emission)
     136                 :            : {
     137         [ #  # ]:          0 :   for (gsize i = 0; i < emission->n_param_values; i++)
     138                 :          0 :     g_value_unset (&emission->param_values[i]);
     139                 :          0 :   g_free (emission->param_values);
     140                 :            : 
     141                 :          0 :   g_closure_unref ((GClosure *) emission->closure);
     142                 :          0 :   g_free (emission);
     143                 :          0 : }
     144                 :            : 
     145                 :            : /* Version of G_VALUE_LCOPY() that allows %NULL return locations. */
     146                 :            : #define VALUE_LCOPY(value, var_args, __error) \
     147                 :            : G_STMT_START { \
     148                 :            :   const GValue *_value = (value); \
     149                 :            :   GType _value_type = G_VALUE_TYPE (_value); \
     150                 :            :   GTypeValueTable *_vtable = g_type_value_table_peek (_value_type); \
     151                 :            :   const gchar *_lcopy_format = _vtable->lcopy_format; \
     152                 :            :   GTypeCValue _cvalues[G_VALUE_COLLECT_FORMAT_MAX_LENGTH] = { { 0, }, }; \
     153                 :            :   guint _n_values = 0; \
     154                 :            :   \
     155                 :            :   while (*_lcopy_format != '\0') \
     156                 :            :     { \
     157                 :            :       g_assert (*_lcopy_format == G_VALUE_COLLECT_POINTER); \
     158                 :            :       _cvalues[_n_values++].v_pointer = va_arg ((var_args), gpointer); \
     159                 :            :       _lcopy_format++; \
     160                 :            :     } \
     161                 :            :   \
     162                 :            :   if (_n_values == 2 && !!_cvalues[0].v_pointer != !!_cvalues[1].v_pointer) \
     163                 :            :     *(__error) = g_strdup_printf ("all return locations need the same nullability"); \
     164                 :            :   else if (_cvalues[0].v_pointer != NULL) \
     165                 :            :     *(__error) = _vtable->lcopy_value (_value, _n_values, _cvalues, 0); \
     166                 :            : } G_STMT_END
     167                 :            : 
     168                 :            : /**
     169                 :            :  * gt_signal_logger_emission_get_params:
     170                 :            :  * @self: a #GtSignalLoggerEmission
     171                 :            :  * @...: return locations for the signal parameters
     172                 :            :  *
     173                 :            :  * Get the parameters emitted in this signal emission. They are returned in the
     174                 :            :  * return locations provided as varargs. These locations must have the right
     175                 :            :  * type for the parameters of the signal which was emitted.
     176                 :            :  *
     177                 :            :  * To ignore a particular parameter, pass %NULL as the one (or more) return
     178                 :            :  * locations for that parameter.
     179                 :            :  *
     180                 :            :  * Since: 0.1.0
     181                 :            :  */
     182                 :            : void
     183                 :          0 : gt_signal_logger_emission_get_params (GtSignalLoggerEmission *self,
     184                 :            :                                       ...)
     185                 :            : {
     186                 :            :   va_list ap;
     187                 :            : 
     188                 :          0 :   va_start (ap, self);
     189                 :            : 
     190         [ #  # ]:          0 :   for (gsize i = 0; i < self->n_param_values; i++)
     191                 :            :     {
     192                 :          0 :       g_autofree gchar *error_message = NULL;
     193   [ #  #  #  #  :          0 :       VALUE_LCOPY (&self->param_values[i], ap, &error_message);
          #  #  #  #  #  
                      # ]
     194                 :            : 
     195                 :            :       /* Error messages are not fatal, as they typically indicate that the user
     196                 :            :        * has passed in %NULL rather than a valid return pointer. We can recover
     197                 :            :        * from that. */
     198         [ #  # ]:          0 :       if (error_message != NULL)
     199                 :          0 :         g_debug ("Error copying GValue %" G_GSIZE_FORMAT " from emission of %s::%s from %p: %s",
     200                 :            :                  i, self->closure->obj_type_name, self->closure->signal_name,
     201                 :            :                  self->closure->obj, error_message);
     202                 :            :     }
     203                 :            : 
     204                 :          0 :   va_end (ap);
     205                 :          0 : }
     206                 :            : 
     207                 :            : static void
     208                 :          0 : gt_logged_closure_marshal (GClosure     *closure,
     209                 :            :                            GValue       *return_value,
     210                 :            :                            guint         n_param_values,
     211                 :            :                            const GValue *param_values,
     212                 :            :                            gpointer      invocation_hint,
     213                 :            :                            gpointer      marshal_data)
     214                 :            : {
     215                 :          0 :   GtLoggedClosure *self = (GtLoggedClosure *) closure;
     216                 :            : 
     217                 :            :   /* Log the @param_values. Ignore the @return_value, and the first of
     218                 :            :    * @param_values (which is the object instance). */
     219         [ #  # ]:          0 :   g_assert (n_param_values >= 1);
     220                 :            : 
     221                 :          0 :   g_autoptr(GtSignalLoggerEmission) emission = g_new0 (GtSignalLoggerEmission, 1);
     222                 :          0 :   emission->closure = (GtLoggedClosure *) g_closure_ref ((GClosure *) self);
     223                 :          0 :   emission->n_param_values = n_param_values - 1;
     224                 :          0 :   emission->param_values = g_new0 (GValue, emission->n_param_values);
     225                 :            : 
     226         [ #  # ]:          0 :   for (gsize i = 0; i < emission->n_param_values; i++)
     227                 :            :     {
     228                 :          0 :       g_value_init (&emission->param_values[i], G_VALUE_TYPE (&param_values[i + 1]));
     229                 :          0 :       g_value_copy (&param_values[i + 1], &emission->param_values[i]);
     230                 :            :     }
     231                 :            : 
     232                 :          0 :   g_ptr_array_add (self->logger->log, g_steal_pointer (&emission));
     233                 :          0 : }
     234                 :            : 
     235                 :            : static void
     236                 :          0 : gt_logged_closure_invalidate (gpointer  user_data,
     237                 :            :                               GClosure *closure)
     238                 :            : {
     239                 :          0 :   GtLoggedClosure *self = (GtLoggedClosure *) closure;
     240                 :            : 
     241                 :          0 :   self->signal_id = 0;
     242                 :          0 : }
     243                 :            : 
     244                 :            : static void
     245                 :          0 : gt_logged_closure_finalize (gpointer  user_data,
     246                 :            :                             GClosure *closure)
     247                 :            : {
     248                 :          0 :   GtLoggedClosure *self = (GtLoggedClosure *) closure;
     249                 :            : 
     250                 :            :   /* Deliberately don’t g_ptr_array_remove() the closure from the
     251                 :            :    * self->logger->closures list, since finalize() can only be called when the
     252                 :            :    * final reference to the closure is dropped, and self->logger->closures holds
     253                 :            :    * a reference, so we must be being finalised from there (or that GPtrArray
     254                 :            :    * has already been finalised). */
     255                 :            : 
     256                 :          0 :   g_free (self->obj_type_name);
     257                 :          0 :   g_free (self->signal_name);
     258                 :            : 
     259         [ #  # ]:          0 :   g_assert (self->signal_id == 0);
     260                 :          0 : }
     261                 :            : 
     262                 :            : /**
     263                 :            :  * gt_logged_closure_new:
     264                 :            :  * @logger: (transfer none): logger to connect the closure to
     265                 :            :  * @obj: (not nullable) (transfer none): #GObject to connect the closure to
     266                 :            :  * @signal_name: (not nullable): signal name to connect the closure to
     267                 :            :  *
     268                 :            :  * Create a new #GtLoggedClosure for @logger, @obj and @signal_name. @obj must
     269                 :            :  * be a valid object instance at this point (it may later be finalised before
     270                 :            :  * the closure).
     271                 :            :  *
     272                 :            :  * This does not connect the closure to @signal_name on @obj. Use
     273                 :            :  * gt_signal_logger_connect() for that.
     274                 :            :  *
     275                 :            :  * Returns: (transfer full): a new closure
     276                 :            :  * Since: 0.1.0
     277                 :            :  */
     278                 :            : static GClosure *
     279                 :          0 : gt_logged_closure_new (GtSignalLogger *logger,
     280                 :            :                        GObject        *obj,
     281                 :            :                        const gchar    *signal_name)
     282                 :            : {
     283                 :          0 :   g_autoptr(GClosure) closure = g_closure_new_simple (sizeof (GtLoggedClosure), NULL);
     284                 :            : 
     285                 :          0 :   GtLoggedClosure *self = (GtLoggedClosure *) closure;
     286                 :          0 :   self->logger = logger;
     287                 :          0 :   self->obj = obj;
     288                 :          0 :   self->obj_type_name = g_strdup (G_OBJECT_TYPE_NAME (obj));
     289                 :          0 :   self->signal_name = g_strdup (signal_name);
     290                 :          0 :   self->signal_id = 0;
     291                 :            : 
     292                 :          0 :   g_closure_add_invalidate_notifier (closure, NULL, (GClosureNotify) gt_logged_closure_invalidate);
     293                 :          0 :   g_closure_add_finalize_notifier (closure, NULL, (GClosureNotify) gt_logged_closure_finalize);
     294                 :          0 :   g_closure_set_marshal (closure, gt_logged_closure_marshal);
     295                 :            : 
     296                 :          0 :   g_ptr_array_add (logger->closures, g_closure_ref (closure));
     297                 :            : 
     298                 :          0 :   return g_steal_pointer (&closure);
     299                 :            : }
     300                 :            : 
     301                 :            : /**
     302                 :            :  * gt_signal_logger_new:
     303                 :            :  *
     304                 :            :  * Create a new #GtSignalLogger. Add signals to it to log using
     305                 :            :  * gt_signal_logger_connect().
     306                 :            :  *
     307                 :            :  * Returns: (transfer full): a new #GtSignalLogger
     308                 :            :  * Since: 0.1.0
     309                 :            :  */
     310                 :            : GtSignalLogger *
     311                 :          1 : gt_signal_logger_new (void)
     312                 :            : {
     313                 :          2 :   g_autoptr(GtSignalLogger) logger = g_new0 (GtSignalLogger, 1);
     314                 :            : 
     315                 :          1 :   logger->log = g_ptr_array_new_with_free_func ((GDestroyNotify) gt_signal_logger_emission_free);
     316                 :          1 :   logger->closures = g_ptr_array_new_with_free_func ((GDestroyNotify) g_closure_unref);
     317                 :            : 
     318                 :          1 :   return g_steal_pointer (&logger);
     319                 :            : }
     320                 :            : 
     321                 :            : /**
     322                 :            :  * gt_signal_logger_free:
     323                 :            :  * @self: (transfer full): a #GtSignalLogger
     324                 :            :  *
     325                 :            :  * Free a #GtSignalLogger. This will disconnect all its closures from the
     326                 :            :  * signals they are connected to.
     327                 :            :  *
     328                 :            :  * This function may be called when there are signal emissions left in the
     329                 :            :  * logged stack, but typically you will want to call
     330                 :            :  * gt_signal_logger_assert_no_emissions() first.
     331                 :            :  *
     332                 :            :  * Since: 0.1.0
     333                 :            :  */
     334                 :            : void
     335                 :          1 : gt_signal_logger_free (GtSignalLogger *self)
     336                 :            : {
     337         [ -  + ]:          1 :   g_return_if_fail (self != NULL);
     338                 :            : 
     339                 :            :   /* Disconnect all the closures, since we don’t care about logging any more. */
     340         [ -  + ]:          1 :   for (gsize i = 0; i < self->closures->len; i++)
     341                 :            :     {
     342                 :          0 :       GClosure *closure = g_ptr_array_index (self->closures, i);
     343                 :            : 
     344                 :          0 :       g_closure_invalidate (closure);
     345                 :            :     }
     346                 :            : 
     347                 :          1 :   g_ptr_array_unref (self->closures);
     348                 :          1 :   g_ptr_array_unref (self->log);
     349                 :            : 
     350                 :          1 :   g_free (self);
     351                 :            : }
     352                 :            : 
     353                 :            : /**
     354                 :            :  * gt_signal_logger_connect:
     355                 :            :  * @self: a #GtSignalLogger
     356                 :            :  * @obj: (type GObject): a #GObject to connect to
     357                 :            :  * @signal_name: the signal on @obj to connect to
     358                 :            :  *
     359                 :            :  * A convenience wrapper around g_signal_connect() which connects the
     360                 :            :  * #GtSignalLogger to the given @signal_name on @obj so that emissions of it
     361                 :            :  * will be logged.
     362                 :            :  *
     363                 :            :  * The closure will be disconnected (and the returned signal connection ID
     364                 :            :  * invalidated) when:
     365                 :            :  *
     366                 :            :  *   * @obj is finalised
     367                 :            :  *   * The closure is freed or removed
     368                 :            :  *   * The signal logger is freed
     369                 :            :  *
     370                 :            :  * This does not keep a strong reference to @obj.
     371                 :            :  *
     372                 :            :  * Returns: signal connection ID, as returned from g_signal_connect()
     373                 :            :  * Since: 0.1.0
     374                 :            :  */
     375                 :            : gulong
     376                 :          0 : gt_signal_logger_connect (GtSignalLogger *self,
     377                 :            :                           gpointer        obj,
     378                 :            :                           const gchar    *signal_name)
     379                 :            : {
     380         [ #  # ]:          0 :   g_return_val_if_fail (self != NULL, 0);
     381         [ #  # ]:          0 :   g_return_val_if_fail (G_IS_OBJECT (obj), 0);
     382         [ #  # ]:          0 :   g_return_val_if_fail (signal_name != NULL, 0);
     383                 :            : 
     384                 :          0 :   g_autoptr(GClosure) closure = gt_logged_closure_new (self, obj, signal_name);
     385                 :          0 :   GtLoggedClosure *c = (GtLoggedClosure *) closure;
     386                 :          0 :   c->signal_id = g_signal_connect_closure (obj, signal_name, g_closure_ref (closure), FALSE);
     387                 :          0 :   return c->signal_id;
     388                 :            : }
     389                 :            : 
     390                 :            : /**
     391                 :            :  * gt_signal_logger_get_n_emissions:
     392                 :            :  * @self: a #GtSignalLogger
     393                 :            :  *
     394                 :            :  * Get the number of signal emissions which have been logged (and not popped)
     395                 :            :  * since the logger was initialised.
     396                 :            :  *
     397                 :            :  * Returns: number of signal emissions
     398                 :            :  * Since: 0.1.0
     399                 :            :  */
     400                 :            : gsize
     401                 :          1 : gt_signal_logger_get_n_emissions (GtSignalLogger *self)
     402                 :            : {
     403         [ -  + ]:          1 :   g_return_val_if_fail (self != NULL, 0);
     404                 :            : 
     405                 :          1 :   return self->log->len;
     406                 :            : }
     407                 :            : 
     408                 :            : /**
     409                 :            :  * gt_signal_logger_pop_emission:
     410                 :            :  * @self: a #GtSignalLogger
     411                 :            :  * @out_obj: (out) (transfer none) (optional) (not nullable): return location
     412                 :            :  *    for the object instance which emitted the signal
     413                 :            :  * @out_obj_type_name: (out) (transfer full) (optional) (not nullable): return
     414                 :            :  *    location for the name of the type of @out_obj
     415                 :            :  * @out_signal_name: (out) (transfer full) (optional) (not nullable): return
     416                 :            :  *    location for the name of the emitted signal
     417                 :            :  * @out_emission: (out) (transfer full) (optional) (not nullable): return
     418                 :            :  *    location for the signal emission closure containing emission parameters
     419                 :            :  *
     420                 :            :  * Pop the oldest signal emission off the stack of logged emissions, and return
     421                 :            :  * its object, signal name and parameters in the given return locations. All
     422                 :            :  * return locations are optional: if they are all %NULL, this function just
     423                 :            :  * performs a pop.
     424                 :            :  *
     425                 :            :  * If there are no signal emissions on the logged stack, %FALSE is returned.
     426                 :            :  *
     427                 :            :  * @out_obj does not return a reference to the object instance, as it may have
     428                 :            :  * been finalised since the signal emission was logged. It should be treated as
     429                 :            :  * an opaque pointer. The type name of the object is given as
     430                 :            :  * @out_obj_type_name, which is guaranteed to be valid.
     431                 :            :  *
     432                 :            :  * Returns: %TRUE if an emission was popped and returned, %FALSE otherwise
     433                 :            :  * Since: 0.1.0
     434                 :            :  */
     435                 :            : gboolean
     436                 :          0 : gt_signal_logger_pop_emission (GtSignalLogger          *self,
     437                 :            :                                gpointer                *out_obj,
     438                 :            :                                gchar                  **out_obj_type_name,
     439                 :            :                                gchar                  **out_signal_name,
     440                 :            :                                GtSignalLoggerEmission **out_emission)
     441                 :            : {
     442         [ #  # ]:          0 :   g_return_val_if_fail (self != NULL, FALSE);
     443                 :            : 
     444         [ #  # ]:          0 :   if (self->log->len == 0)
     445                 :            :     {
     446         [ #  # ]:          0 :       if (out_obj != NULL)
     447                 :          0 :         *out_obj = NULL;
     448         [ #  # ]:          0 :       if (out_obj_type_name != NULL)
     449                 :          0 :         *out_obj_type_name = NULL;
     450         [ #  # ]:          0 :       if (out_signal_name != NULL)
     451                 :          0 :         *out_signal_name = NULL;
     452         [ #  # ]:          0 :       if (out_emission != NULL)
     453                 :          0 :         *out_emission = NULL;
     454                 :            : 
     455                 :          0 :       return FALSE;
     456                 :            :     }
     457                 :            : 
     458                 :            :   /* FIXME: Could do with g_ptr_array_steal() here.
     459                 :            :    * https://bugzilla.gnome.org/show_bug.cgi?id=795376 */
     460                 :          0 :   g_ptr_array_set_free_func (self->log, NULL);
     461                 :          0 :   g_autoptr(GtSignalLoggerEmission) emission = g_steal_pointer (&self->log->pdata[0]);
     462                 :          0 :   g_ptr_array_remove_index (self->log, 0);
     463                 :          0 :   g_ptr_array_set_free_func (self->log, (GDestroyNotify) gt_signal_logger_emission_free);
     464                 :            : 
     465         [ #  # ]:          0 :   if (out_obj != NULL)
     466                 :          0 :     *out_obj = emission->closure->obj;
     467         [ #  # ]:          0 :   if (out_obj_type_name != NULL)
     468                 :          0 :     *out_obj_type_name = g_strdup (emission->closure->obj_type_name);
     469         [ #  # ]:          0 :   if (out_signal_name != NULL)
     470                 :          0 :     *out_signal_name = g_strdup (emission->closure->signal_name);
     471         [ #  # ]:          0 :   if (out_emission != NULL)
     472                 :          0 :     *out_emission = g_steal_pointer (&emission);
     473                 :            : 
     474                 :          0 :   return TRUE;
     475                 :            : }
     476                 :            : 
     477                 :            : /**
     478                 :            :  * gt_signal_logger_format_emission:
     479                 :            :  * @obj: a #GObject instance which emitted a signal
     480                 :            :  * @obj_type_name: a copy of `G_OBJECT_TYPE_NAME (obj)` for use when @obj may
     481                 :            :  *    be invalid
     482                 :            :  * @signal_name: name of the emitted signal
     483                 :            :  * @emission: details of the signal emission
     484                 :            :  *
     485                 :            :  * Format a signal emission in a human readable form, typically for logging it
     486                 :            :  * to some debug output.
     487                 :            :  *
     488                 :            :  * The returned string does not have a trailing newline character (`\n`).
     489                 :            :  *
     490                 :            :  * @obj may have been finalised, and is just treated as an opaque pointer.
     491                 :            :  *
     492                 :            :  * Returns: (transfer full): human readable string detailing the signal emission
     493                 :            :  * Since: 0.1.0
     494                 :            :  */
     495                 :            : gchar *
     496                 :          0 : gt_signal_logger_format_emission (gpointer                      obj,
     497                 :            :                                   const gchar                  *obj_type_name,
     498                 :            :                                   const gchar                  *signal_name,
     499                 :            :                                   const GtSignalLoggerEmission *emission)
     500                 :            : {
     501         [ #  # ]:          0 :   g_return_val_if_fail (obj != NULL, NULL);  /* deliberately not a G_IS_OBJECT() check */
     502         [ #  # ]:          0 :   g_return_val_if_fail (signal_name != NULL, NULL);
     503         [ #  # ]:          0 :   g_return_val_if_fail (emission != NULL, NULL);
     504                 :            : 
     505                 :          0 :   g_autoptr(GString) str = g_string_new ("");
     506                 :          0 :   g_string_append_printf (str, "%s::%s from %p (",
     507                 :            :                           obj_type_name, signal_name, obj);
     508                 :            : 
     509         [ #  # ]:          0 :   for (gsize i = 0; i < emission->n_param_values; i++)
     510                 :            :     {
     511         [ #  # ]:          0 :       if (i > 0)
     512                 :          0 :         g_string_append (str, ", ");
     513                 :            : 
     514                 :          0 :       g_auto(GValue) str_value = G_VALUE_INIT;
     515                 :          0 :       g_value_init (&str_value, G_TYPE_STRING);
     516                 :            : 
     517         [ #  # ]:          0 :       if (g_value_transform (&emission->param_values[i], &str_value))
     518                 :          0 :         g_string_append (str, g_value_get_string (&str_value));
     519                 :            :       else
     520                 :          0 :         g_string_append_printf (str, "GValue of type %s",
     521                 :          0 :                                 G_VALUE_TYPE_NAME (&emission->param_values[i]));
     522                 :            :     }
     523                 :            : 
     524         [ #  # ]:          0 :   if (emission->n_param_values == 0)
     525                 :          0 :     g_string_append (str, "no arguments");
     526                 :          0 :   g_string_append (str, ")");
     527                 :            : 
     528                 :          0 :   return g_string_free (g_steal_pointer (&str), FALSE);
     529                 :            : }
     530                 :            : 
     531                 :            : /**
     532                 :            :  * gt_signal_logger_format_emissions:
     533                 :            :  * @self: a #GtSignalLogger
     534                 :            :  *
     535                 :            :  * Format all the signal emissions on the logging stack in the #GtSignalLogger,
     536                 :            :  * in a human readable format, one per line. The returned string does not end
     537                 :            :  * in a newline character (`\n`). Each signal emission is formatted using
     538                 :            :  * gt_signal_logger_format_emission().
     539                 :            :  *
     540                 :            :  * Returns: (transfer full): human readable list of all the signal emissions
     541                 :            :  *    currently in the logger, or an empty string if the logger is empty
     542                 :            :  * Since: 0.1.0
     543                 :            :  */
     544                 :            : gchar *
     545                 :          0 : gt_signal_logger_format_emissions (GtSignalLogger *self)
     546                 :            : {
     547         [ #  # ]:          0 :   g_return_val_if_fail (self != NULL, NULL);
     548                 :            : 
     549                 :            :   /* Work out the width of the counter we need to number the emissions. */
     550                 :          0 :   guint width = 1;
     551                 :          0 :   gsize n_emissions = self->log->len;
     552         [ #  # ]:          0 :   while (n_emissions >= 10)
     553                 :            :     {
     554                 :          0 :       n_emissions /= 10;
     555                 :          0 :       width++;
     556                 :            :     }
     557                 :            : 
     558                 :            :   /* Format each emission and list them. */
     559                 :          0 :   g_autoptr(GString) str = g_string_new ("");
     560                 :            : 
     561         [ #  # ]:          0 :   for (gsize i = 0; i < self->log->len; i++)
     562                 :            :     {
     563                 :          0 :       const GtSignalLoggerEmission *emission = g_ptr_array_index (self->log, i);
     564                 :            : 
     565         [ #  # ]:          0 :       if (i > 0)
     566                 :          0 :         g_string_append (str, "\n");
     567                 :            : 
     568                 :          0 :       g_autofree gchar *emission_str = gt_signal_logger_format_emission (emission->closure->obj,
     569                 :          0 :                                                                          emission->closure->obj_type_name,
     570                 :          0 :                                                                          emission->closure->signal_name,
     571                 :            :                                                                          emission);
     572                 :          0 :       g_string_append_printf (str, " %*" G_GSIZE_FORMAT ". %s", (int) width, i + 1, emission_str);
     573                 :            :     }
     574                 :            : 
     575                 :          0 :   return g_string_free (g_steal_pointer (&str), FALSE);
     576                 :            : }

Generated by: LCOV version 1.14