LCOV - code coverage report
Current view: top level - libmogwai-schedule/tests - signal-logger.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 90 136 66.2 %
Date: 2022-06-30 20:59:16 Functions: 11 13 84.6 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 28 76 36.8 %

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

Generated by: LCOV version 1.16